Experimental API reference¶
These modules diagnose dependence and validate collection designs. Their interfaces may change before the stable inference API does.
Spatial and temporal dependence diagnostics for geoestimate.
These experimental functions measure within-cluster correlation and how fast
it decays along geographic distance or elapsed time. They describe dependence;
they do not replace the standard-error estimators in geoestimate.inference.
other pairwise-distance matrix. They are diagnostics: they describe the
dependence structure (and how much information itineraries cost), they do not
replace the standard-error estimators in inference.py.
The core is axis-agnostic. empirical_variogram, morans_i, and
effective_n all take a precomputed n×n distance matrix, so the same code
serves the spatial axis (haversine_matrix), the temporal axis
(time_gap_matrix), and any future axis (e.g. same/different enumerator).
References
- Griffith, D.A. (2005). Effective geographic sample size in the presence
of spatial autocorrelation.
- Watson, P.A. (2021). A note on the variogram-based effective sample size.
Applied Statistics.
- class geoestimate.spatial.DependenceDiagnostics(within_between_ratio=nan, morans_i_space=nan, morans_i_space_p=nan, variogram_range_m=nan, spatial_corr_ratio=nan, n_eff_space=nan, morans_i_time=nan, morans_i_time_p=nan, variogram_range_s=nan, temporal_corr_ratio=nan, n_eff_time=nan)[source]¶
Experimental spatial and temporal dependence diagnostics.
- geoestimate.spatial.haversine_matrix(lon, lat)[source]¶
Pairwise great-circle distances in meters between lon/lat points.
- Parameters:
lon (ndarray) – Longitudes in decimal degrees.
lat (ndarray) – Latitudes in decimal degrees.
- Returns:
n×n symmetric matrix of distances in meters (zero diagonal).
- Raises:
ValueError – If coordinates are misaligned, nonfinite, or outside valid longitude and latitude bounds.
- Return type:
ndarray
- geoestimate.spatial.time_gap_matrix(timestamps)[source]¶
Pairwise absolute time differences in seconds.
- Parameters:
timestamps (ndarray) – datetime64 / Timestamps / ISO strings, or numeric epoch seconds.
- Returns:
n×n symmetric matrix of absolute time gaps in seconds (zero diagonal).
- Return type:
ndarray
- geoestimate.spatial.empirical_variogram(values, dist, n_bins=15, max_dist=None)[source]¶
Classical (Matheron) empirical semivariogram over distance bins.
Semivariance per bin is the mean of
0.5 * (z_i - z_j)**2over unique pairs whose distance falls in the bin.- Parameters:
- Returns:
(lag_centers, semivariance, pair_counts) for non-empty bins only.
- Raises:
ValueError – If inputs are malformed or outside their domains.
- Return type:
tuple[ndarray, ndarray, ndarray]
- geoestimate.spatial.fit_variogram(lags, gamma, counts, model='exponential')[source]¶
Weighted least-squares fit of an exponential variogram.
The fitted correlation function is
rho(h) = ((C1-C0)/C1) * exp(-h/r), soris the e-folding scale (correlation falls to 1/e at h = r); the conventional “effective range” is ~3r.- Parameters:
lags (ndarray) – Bin-center distances from
empirical_variogram.gamma (ndarray) – Semivariances from
empirical_variogram.counts (ndarray) – Pair counts per bin (used as fit weights).
model (str) – Only
"exponential"is supported.
- Returns:
(nugget C0, sill C1, range r). NaNs if the fit cannot be performed.
- Raises:
ValueError – If
modelnames anything but"exponential".- Return type:
- geoestimate.spatial.morans_i(values, dist, cutoff, n_perm=999, seed=0)[source]¶
Global Moran’s I with binary distance-cutoff weights.
Weight
w_ij = 1if0 < dist_ij <= cutoffelse 0. The p-value is a two-sided permutation test on the observed statistic.- Parameters:
- Returns:
(I, p_value). NaNs if undefined (e.g. no neighbor pairs).
- Raises:
TypeError – If
seedis not an integer.ValueError – If inputs are malformed or outside their domains.
- Return type:
- geoestimate.spatial.effective_n(values, dist, nugget, sill, range_)[source]¶
Variogram-based effective sample size under autocorrelation.
n_eff = n / (1 + (1/n) * sum_{i!=j} rho(d_ij))with the exponential correlationrho(d) = ((sill - nugget)/sill) * exp(-d/range_)(Griffith 2005; Watson 2021). Reduces tonwhen there is no autocorrelation.- Parameters:
- Returns:
Effective sample size (<= n), or n if the fit was degenerate.
- Raises:
ValueError – If the distance matrix or variogram parameters are outside their supported domains.
- Return type:
- geoestimate.spatial.within_between_contrast(values, labels)[source]¶
Mean semivariance of same-cluster vs different-cluster pairs.
This is the “does the prior point predict the next one, versus a point elsewhere?” contrast.
ratio = within / betweennear 1 means itineraries look like representative subsamples (dispersed);ratiowell below 1 means within-itinerary pairs are much more alike (compact routes), which inflates the design effect.- Parameters:
values (ndarray) – One value per observation.
labels (ndarray) – Cluster label per observation, aligned to
values.
- Returns:
{"within", "between", "ratio"}(semivariances; NaN where undefined).- Raises:
ValueError – If values and labels are malformed or misaligned.
- Return type:
- geoestimate.spatial.dependence_diagnostics(values, labels, *, lon=None, lat=None, timestamps=None, max_points=2500, seed=0)[source]¶
Compute experimental within-cluster dependence diagnostics.
- Parameters:
values (ndarray) – One finite outcome per observation.
labels (ndarray) – Cluster labels aligned with
values.lon (ndarray | None) – Optional longitudes in decimal degrees.
lat (ndarray | None) – Optional latitudes in decimal degrees.
timestamps (ndarray | None) – Optional datetimes or epoch seconds.
max_points (int) – Maximum observations used for pairwise diagnostics.
seed (int) – Random seed used for subsampling and permutations.
- Returns:
Spatial and temporal descriptive diagnostics.
- Raises:
TypeError – If
seedis not an integer.ValueError – If arrays are misaligned, incomplete, or outside their documented domains.
- Return type:
Space-time Monte Carlo harness for stress-testing itinerary-based collection.
The point of this module is limiting-case reasoning. We simulate a known
space-time data-generating process (DGP) with no measurement noise, run real
collection pipelines (sampling + routing under a fixed per-route time budget)
on top, push the assumptions to their extremes, and check what happens to the
bias, standard-error calibration, and confidence-interval coverage that
geoestimate.Sample produces.
The analytically-predictable limits (the “truth table”):
- Spatial correlation length
range_s_mvs. the route’s inter-point spacing 0 (white noise): information ∝ point count → COMPACT routing is BLUE; ICC≈0, deff≈1, naive SE correct.
∞ (field constant in space): n_eff_space → 1; extra spatial points carry no information; only the time axis can buy precision.
- Temporal structure (
diurnal_amp,range_t_min, start-time policy) none: start-time policy is irrelevant, no temporal bias.
strong diurnal + synchronized starts: β̂ is biased toward the sampled time-of-day by a fixed amount; more points do NOT fix it; staggering start times across the day does.
- Key separations the extremes make obvious:
Correlation moves VARIANCE / SE / coverage, never the bias of the mean.
Bias comes from SELECTION: spatial coverage gaps and non-representative time windows.
The coverage cliff (naive SE under compact routing) is an INTERIOR phenomenon: benign at both correlation extremes, worst in the middle.
Everything works in lon/lat with great-circle distances so it exercises the same code path as real data.
- class geoestimate.simulate.SimConfig(grid_n=18, extent_deg=0.05, range_s_m=800.0, sd_s=1.0, base_logit=-0.85, day_min=600.0, range_t_min=60.0, diurnal_amp=0.0, sd_t=0.0, time_grid_n=40, n_itineraries=8, shift_min=120.0, speed_m_per_min=80.0, dwell_min=2.0, n_sims=200, seed=12345)[source]¶
Parameters of the space-time DGP and the field operation.
Defaults describe a ~5.5 km square region surveyed in shifts of a few hours. Push any parameter to an extreme to read off a limiting case.
- Parameters:
- class geoestimate.simulate.Population(lon, lat, dist_m, g_s, t_grid, temporal, base_logit, beta_true)[source]¶
One realized space-time population (no measurement noise).
- Parameters:
- class geoestimate.simulate.PopulationFactory(cfg, lon=None, lat=None)[source]¶
Precomputes the geometry + covariance factors, then draws populations.
Factoring once per configuration keeps the Monte Carlo loop cheap: each draw is just two matrix-vector products.
- Parameters:
cfg (SimConfig)
lon (ndarray | None)
lat (ndarray | None)
- class geoestimate.simulate.Pipeline(name, routing='compact', staggered_starts=True)[source]¶
A named collection strategy.
- Parameters:
routing (str) – How the next point is chosen. “compact” selects the nearest unvisited, “dispersed” (farthest from already-visited, i.e. maximin), “systematic” (a fixed space-filling order from a random offset), or “srs” (uniformly random next).
staggered_starts (bool) – If True, route start times are spread across the day; if False, all routes start at the same time-of-day (synchronized).
name (str)
- geoestimate.simulate.collect(pop, pipe, cfg, rng)[source]¶
Run all K routes of a pipeline and return an annotated-frame DataFrame.
- Parameters:
pop (Population)
pipe (Pipeline)
cfg (SimConfig)
rng (Generator)
- Return type:
DataFrame
- class geoestimate.simulate.PipelineResult(name, se_method, n_sims, mean_n, mean_dist_km, bias, true_sd, mean_se, se_sd_ratio, coverage, mean_n_eff_space, mean_within_between)[source]¶
Monte Carlo metrics for one (pipeline, SE method) combination.
biasandtrue_sddescribe the sampling distribution of beta-hat across sims;se_sd_ratio(mean SE / true SD) andcoveragedescribe whether the reported uncertainty is honest.- Parameters:
- geoestimate.simulate.run_pipeline(factory, pipe, cfg, se_method='auto', ci_level=0.95, spatial_diag=False)[source]¶
Monte Carlo a single synthetic pipeline; return aggregated metrics.
- Parameters:
factory (PopulationFactory) – Precomputed population factory (geometry + covariance).
pipe (Pipeline) – The collection strategy to simulate (synthetic routing).
cfg (SimConfig) – Simulation configuration (DGP + field operation + Monte Carlo).
se_method (str) – Which standard error drives the CI: “auto”, “naive”, “cluster” (analytic robust with t_{G-1}), or “boot” (pairs bootstrap).
ci_level (float) – Nominal confidence level.
spatial_diag (bool) – If True, also collect the spatial dependence diagnostics.
- Returns:
A
PipelineResultovercfg.n_simssimulations.- Raises:
ValueError – If the method or confidence level is unsupported.
- Return type:
- geoestimate.simulate.evaluate_scene(factory, sample_idx, itinerary_id, time_of_day_min, timestamp_s, cfg, se_method='auto', ci_level=0.95, spatial_diag=True, label='scene')[source]¶
Design-conditional Monte Carlo of a realized survey against the city mean.
factorycarries the field on the whole city universe; the survey is a fixed subsample of it (sample_idx) with a fixed itinerary partition and visit times (you ran geo_sampling + the allocator once). Each simulation redraws the outcome field, estimates the ratio from the observed sample, and checks whether the CI covers the universe space-time mean. Because the sample is a strict subset of the universe, β̂ carries genuine spatial sampling error. The cluster SE must capture that error, so this is a real test of whetherestimate’s SE/CI is honest for that design.- Parameters:
factory (PopulationFactory) –
PopulationFactory(cfg, lon, lat)built on ALL city points.sample_idx (ndarray) – Indices (into the factory universe) of the observed frames.
itinerary_id (ndarray) – Per-frame cluster labels, aligned to
sample_idx.time_of_day_min (ndarray) – Per-frame time-of-day in
[0, day_min]minutes. This drives the diurnal/temporal field component.timestamp_s (ndarray) – Per-frame absolute timestamp in seconds across the whole operation, used by the optional temporal diagnostics.
cfg (SimConfig) – Simulation configuration (DGP + Monte Carlo).
se_method (str) – SE estimator to use, or “auto” to take the design’s.
ci_level (float) – Coverage the interval claims.
spatial_diag (bool) – Whether to accumulate the spatial dependence diagnostics.
label (str) – Display name for the result row.
- Returns:
A
PipelineResultovercfg.n_simsfield redraws.- Raises:
ValueError – If the method, confidence level, or scene arrays are invalid.
- Return type:
- geoestimate.simulate.run_experiment(cfg, pipelines, se_method='auto', ci_level=0.95, spatial_diag=False)[source]¶
Run every pipeline against one shared population factory (one DGP).
- geoestimate.simulate.results_table(results)[source]¶
Render results as a fixed-width table.
- Parameters:
results (list[PipelineResult])
- Return type:
- geoestimate.simulate.default_pipelines()[source]¶
The four reference routing strategies, all with staggered start times.
- geoestimate.simulate.main()[source]¶
Reproduce the headline experiments and print them as tables.
Run with
python -m geoestimate.simulate.- Return type:
None
Bridge from the real geosensing pipeline to a geoestimate simulation scene.
This turns the actual geo_sampling to allocator output into a fixed
scene with point coordinates, itinerary membership, and visit times. The scene
geoestimate.simulate.evaluate_scene can validate a DGP against, and that
mirrors what the annotated frames look like in production.
allocator and geo_sampling are optional; install them with
pip install geoestimate[pipeline] (or uv pip install -e ../allocator
../geo_sampling for local checkouts). They are imported lazily so core
geoestimate keeps no heavy dependencies.
- class geoestimate.pipeline.Scene(longitude, latitude, itinerary_id, timestamp_s, time_of_day_min)[source]¶
A realized field operation: where, in which itinerary, and when.
All arrays are per annotated frame and aligned by position.
- Parameters:
longitude (ndarray)
latitude (ndarray)
itinerary_id (ndarray)
timestamp_s (ndarray)
time_of_day_min (ndarray)
- geoestimate.pipeline.points_from_roads(roads, per_segment=1)[source]¶
Point locations along road segments, from the road-segment schema.
Accepts a DataFrame or a CSV path with the
geo_sampling/allocatorcolumnsstart_lat, start_long, end_lat, end_long(and returnslongitude/latitude). If the frame already haslongitude/latitudeit is returned unchanged.per_segmentinterpolates that many evenly-spaced points along each segment (1 = midpoint). Densifying gives a larger candidate universe and hence a small sampling fraction for realistic validation.
- geoestimate.pipeline.build_itineraries(points, method='random_partition', n_itineraries=None, max_distance=None, seed=None)[source]¶
Partition points into itineraries with
allocator(offline haversine).- Parameters:
points (DataFrame) – Table containing longitude and latitude columns.
method (str) – Allocator partitioning method.
n_itineraries (int | None) – Requested itinerary count, when supported by the method.
max_distance (float | None) – Optional maximum route distance in meters.
seed (int | None) – Optional allocator random seed.
- Returns:
The allocator data table and routes as point-index lists in visit order.
- Raises:
ImportError – If the optional allocator package is unavailable.
TypeError – If
seedis not an integer or None.ValueError – If points or options are outside their supported domains.
RuntimeError – If allocator does not return a complete partition.
- Return type:
- geoestimate.pipeline.assign_visit_times(points, routes, *, days=14, shifts_per_day=1, speed_m_per_min=80.0, dwell_min=2.0, day_minutes=600.0, stagger_starts=True, seed=None)[source]¶
Spread itineraries over a multi-day operation and time every frame.
Each itinerary is a single shift assigned to one (day, slot). Within a shift, visit times accumulate along the route as travel (haversine metres /
speed_m_per_min) plus a per-pointdwell_min. Shifts are spread round-robin acrossdays × shifts_per_dayso timestamps span the whole operation while each shift’s frames stay close in time-of-day.Returns a
Scenewhosetime_of_day_mindrives the diurnal field and whosetimestamp_sis the absolute time fed toestimate’s temporal diagnostic.
- geoestimate.pipeline.make_scene(roads, method='random_partition', n_itineraries=200, seed=0, **time_kwargs)[source]¶
Convenience: roads CSV/frame → points → itineraries → timed
Scene.
- geoestimate.pipeline.subsample_scene(universe, n_sample, method='kmeans_tsp', n_itineraries=80, seed=0, stagger_starts=True, days=14, shifts_per_day=None)[source]¶
Sample a survey out of a city universe and route it into itineraries.
Treats
universe(all candidate road segments for a city) as the population, drawsn_sampleof them (SRS as a stand-in for livegeo_sampling), and routes the sample with the allocator. Returns(sample_idx, scene)wheresample_idxindexes the universe (so the field can be drawn on the whole city and the sample scored against the city mean ingeoestimate.simulate.evaluate_scene).