Skip to content

Add spatstat-equivalent edge corrections to G, F, J, K, and L#211

Draft
sjsrey wants to merge 10 commits into
pysal:mainfrom
sjsrey:feat/edge
Draft

Add spatstat-equivalent edge corrections to G, F, J, K, and L#211
sjsrey wants to merge 10 commits into
pysal:mainfrom
sjsrey:feat/edge

Conversation

@sjsrey

@sjsrey sjsrey commented Jun 30, 2026

Copy link
Copy Markdown
Member

Summary

  • Implements the full spatstat suite of edge corrections for all five distance-based summary functions: G, F, J, K, and L
  • Follows the established _NOTSET sentinel pattern: omitting edge_correction returns a named tuple with all corrections simultaneously (GEstResult, FEstResult, JEstResult); passing edge_correction=None returns the uncorrected (support, values) tuple for backward compatibility
  • Adds max_radius helper in geometry.py for erosion-threshold computation

Corrections added

Function Corrections
G raw, rs (border/erosion), km (Kaplan-Meier), hanisch
F raw, rs, km, cs (Chiu-Stoyan)
J un, rs, km, han (Hanisch/CS hybrid)
K None (raw), border/erosion, isotropic, translate
L same as K

Key implementation notes

  • Chiu-Stoyan weight formula: weight = π d² / area(disk(u,d) ∩ W) — the fraction of the disk visible from inside W. An earlier approach using |W| / area(disk ∩ W) diverges as 1/d² for small empty-space distances and was discarded.
  • J support alignment: fixed a shape-mismatch crash when G-rs clips its support to the erosion threshold and the resulting array length differs from the requested support.
  • Kaplan-Meier helper: _kaplan_meier_cdf and _km_at_support compute the product-limit estimator and interpolate it onto an arbitrary support grid.

Test plan

  • pytest pointpats/tests/test_distance_statistics.py — 171 tests covering all correction modes for G, F, J (raw/rs/km/Hanisch/CS/han/un), plus all existing K and L tests
  • Verify default named-tuple fields match explicit single-correction calls for G km, F km/cs, K isotropic/translate
  • Check CSR pattern: corrected estimates track 1 - exp(-λπr²) and πr² theoretical curves
  • Notebook docs/user-guide/edge_corrections.ipynb executes end-to-end without errors (34 cells, 16 code cells)

sjsrey and others added 10 commits June 24, 2026 08:32
Implements the erosion estimator for g(): points whose distance to the
study window boundary is less than r are excluded from the guard set at
each radius, and the support is automatically clipped to the erosion
threshold radius from max_radius(..., method='erosion_threshold'). Adds
hull parameter to g() and 18 new tests covering both the standard and
erosion paths.
Implements the guard-area erosion estimator for k(): only points whose
distance to the study window boundary exceeds r act as focal points at
each radius, with the support auto-clipped to the erosion threshold from
max_radius. Adds hull parameter to k() and l(), threads hull through
_ripley_test for K/L calltypes, and extracts a shared _hull_to_poly
helper used by both g() and k(). Adds 21 new tests covering standard K
behaviour, all three hull types, the CSR K≈πr² sanity check, and L
consistency with K.
Replace squareform() with two memory-efficient paths: when distances are
not pre-supplied, use a radius-query tree so no pairwise distance matrix
is built at all; when the user provides a condensed vector, work from
triu_indices + int8 weight arrays instead of expanding to a full n×n
float64 matrix. The non-erosion path is unchanged.
Implements edge_correction='ripley' for k() and l(): for each point
within r of the window boundary, n_circle equally-spaced test points
are placed on a circle of radius r and w_i = n_circle / n_inside.
Adds TestKRipley (10 tests) and TestLRipley (3 tests); updates the
existing invalid-correction test now that 'ripley' is a valid value.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements edge_correction='analytic' for k() and l(): for each point
within r of the window boundary, w_i = πr² / area(circle(i,r) ∩ window),
computed exactly via shapely intersection. Refactors the 'ripley' and
'analytic' paths to share a single weighted-K loop, selecting only the
weight function at the branch point. Adds TestKAnalytic (11 tests) and
TestLAnalytic (2 tests), including a test pinning that K_analytic ≤
K_ripley due to area fraction ≥ arc fraction for convex windows.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
k() and l() now return KEstResult / LEstResult named tuples (support,
theo, border, isotropic, translate) when called without edge_correction,
mirroring spatstat::Kest() behaviour. Passing edge_correction=None
retains the old uncorrected 2-tuple return for backward compatibility.
A _NOTSET sentinel distinguishes "no argument" from "explicit None".

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Deleted _ripley_circle_weights, _ripley_analytic_weights, and the
'ripley'/'analytic' edge_correction options from k() and l(). Also
removed the n_circle parameter (it was only used by the ripley
correction). The three supported corrections are now border/erosion,
isotropic, and translate. Tests for the removed corrections dropped.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements rs, km, Hanisch (G), Chiu-Stoyan (F), and han/un (J)
corrections following the spatstat pattern: omitting edge_correction
returns a named-tuple (GEstResult/FEstResult/JEstResult) with all
corrections; edge_correction=None returns the raw estimate for
backward compatibility.

Fixes Chiu-Stoyan weight formula: use π d²/area(disk∩W) rather than
|W|/area(disk∩W), which diverged for small empty-space distances.
Fixes J support-alignment when G-rs clips its grid to the erosion
threshold. Refactors test suite to cover all new correction modes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@codecov

codecov Bot commented Jun 30, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 86.68281% with 55 lines in your changes missing coverage. Please review.
✅ Project coverage is 80.2%. Comparing base (a798e9e) to head (bf3a7be).
⚠️ Report is 5 commits behind head on main.

Files with missing lines Patch % Lines
pointpats/distance_statistics.py 91.0% 33 Missing ⚠️
pointpats/geometry.py 51.1% 22 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff           @@
##            main    #211     +/-   ##
=======================================
+ Coverage   72.9%   80.2%   +7.4%     
=======================================
  Files         12      12             
  Lines       2118    2489    +371     
=======================================
+ Hits        1543    1997    +454     
+ Misses       575     492     -83     
Files with missing lines Coverage Δ
pointpats/geometry.py 71.1% <51.1%> (+16.5%) ⬆️
pointpats/distance_statistics.py 77.7% <91.0%> (+62.1%) ⬆️

... and 2 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@sjsrey sjsrey marked this pull request as draft June 30, 2026 17:20
@sjsrey sjsrey requested a review from ljwolf June 30, 2026 17:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant