stireg 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- stireg-0.1.0/.gitignore +11 -0
- stireg-0.1.0/CHANGELOG.md +23 -0
- stireg-0.1.0/CITATION.cff +17 -0
- stireg-0.1.0/LICENSE +21 -0
- stireg-0.1.0/PKG-INFO +171 -0
- stireg-0.1.0/README.md +152 -0
- stireg-0.1.0/examples/manuscript_workflow.py +40 -0
- stireg-0.1.0/pyproject.toml +42 -0
- stireg-0.1.0/requirements.txt +10 -0
- stireg-0.1.0/src/stireg/__init__.py +42 -0
- stireg-0.1.0/src/stireg/change.py +105 -0
- stireg-0.1.0/src/stireg/data.py +117 -0
- stireg-0.1.0/src/stireg/diagnostics.py +66 -0
- stireg-0.1.0/src/stireg/intensity.py +160 -0
- stireg-0.1.0/src/stireg/interaction.py +192 -0
- stireg-0.1.0/src/stireg/simulation.py +386 -0
- stireg-0.1.0/tests/test_change.py +28 -0
- stireg-0.1.0/tests/test_data.py +25 -0
- stireg-0.1.0/tests/test_diagnostics.py +27 -0
- stireg-0.1.0/tests/test_intensity.py +62 -0
- stireg-0.1.0/tests/test_interaction.py +45 -0
- stireg-0.1.0/tests/test_simulation.py +88 -0
stireg-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable research-software changes are documented here. Versions before 1.0 are research prototypes and may change their statistical interface.
|
|
4
|
+
|
|
5
|
+
## 0.1.0 - 2026-10-03
|
|
6
|
+
|
|
7
|
+
- Promoted the first submission-ready public API and aligned version metadata across the package, source distribution, and citation file.
|
|
8
|
+
- Added an executable manuscript workflow with recorded output.
|
|
9
|
+
- Expanded software documentation, API contracts, failure modes, bootstrap details, and the replication map for JSS review.
|
|
10
|
+
- Clarified the package name as *spatio-temporal interaction regime* while retaining a narrow single-change estimand.
|
|
11
|
+
|
|
12
|
+
## 0.0.1.dev0 - 2026-09-28
|
|
13
|
+
|
|
14
|
+
- Established a new project independent of ST-SCKM and rejected the original clustering-like regime-label formulation.
|
|
15
|
+
- Added validated rectangular event-pattern data structures.
|
|
16
|
+
- Added exact within-block border exposure and inverse-intensity weighted pair scores.
|
|
17
|
+
- Added indexed and brute-force pair enumeration with agreement tests.
|
|
18
|
+
- Added a maximum-CUSUM single-change test with complete-vector multiplier resampling.
|
|
19
|
+
- Added Poisson, clustered-snapshot, interaction-change, and independent-thinning simulators.
|
|
20
|
+
- Added pair-information diagnostics.
|
|
21
|
+
- Added deterministic calibration and robustness scripts with stored results.
|
|
22
|
+
- Added reproducible acquisition and preprocessing for a fixed USGS ComCat application.
|
|
23
|
+
- Recorded the unresolved limits on plug-in intensity, sparse components, multiple changes, and non-Poisson temporal dependence.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
cff-version: 1.2.0
|
|
2
|
+
message: "If you use this research software, cite the archived release and associated article when available."
|
|
3
|
+
title: "stireg: Scale-resolved pair-interaction change detection for spatio-temporal point patterns"
|
|
4
|
+
type: software
|
|
5
|
+
authors:
|
|
6
|
+
- family-names: Idris
|
|
7
|
+
given-names: Muh Akbar
|
|
8
|
+
orcid: "https://orcid.org/0009-0000-2995-1975"
|
|
9
|
+
version: 0.1.0
|
|
10
|
+
date-released: 2026-10-03
|
|
11
|
+
license: MIT
|
|
12
|
+
abstract: >-
|
|
13
|
+
Research software for estimating intensity-reweighted, scale-resolved
|
|
14
|
+
spatio-temporal event-pair scores and testing a retrospective change in
|
|
15
|
+
their blockwise means. The current version has a deliberately restricted
|
|
16
|
+
scope and should not be described as a general-purpose interaction-regime
|
|
17
|
+
detector.
|
stireg-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Muh Akbar Idris
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
stireg-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: stireg
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Scale-resolved pair-interaction change detection for spatio-temporal point patterns
|
|
5
|
+
Author: Muh Akbar Idris
|
|
6
|
+
License: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Requires-Dist: numpy>=1.24
|
|
10
|
+
Requires-Dist: scipy>=1.10
|
|
11
|
+
Provides-Extra: applications
|
|
12
|
+
Requires-Dist: pandas>=2.0; extra == 'applications'
|
|
13
|
+
Requires-Dist: pyproj>=3.6; extra == 'applications'
|
|
14
|
+
Requires-Dist: pyshp>=2.3; extra == 'applications'
|
|
15
|
+
Provides-Extra: test
|
|
16
|
+
Requires-Dist: pytest-cov>=5; extra == 'test'
|
|
17
|
+
Requires-Dist: pytest>=8; extra == 'test'
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
20
|
+
# stireg
|
|
21
|
+
|
|
22
|
+
`stireg` is research software for retrospective analysis of changes in localized, scale-resolved second-order interaction in spatio-temporal event patterns. It is independent of ST-SCKM and does not use a clustering objective, centroid update, or graph penalty.
|
|
23
|
+
|
|
24
|
+
The broad “interaction regime” label remains provisional. A 2026 preprint named ST-Score already addresses sequential change detection and continuous-region localization for spatio-temporal point processes. This project therefore targets a narrower estimand: changes in intensity-reweighted event-pair projections indexed by prespecified spatial cells and spatial-temporal lag bins.
|
|
25
|
+
|
|
26
|
+
## Current statistical scope
|
|
27
|
+
|
|
28
|
+
The implemented software supports one unmarked continuous event pattern in a rectangular planar window. Both events in each pair must lie in the same temporal block, cells must be inside the window eroded by the maximum spatial lag, and intensities must be known or supplied at the event locations. The change procedure tests one retrospective mean change in the complete vector of blockwise pair scores.
|
|
29
|
+
|
|
30
|
+
The following are not yet validated: automatic intensity estimation, arbitrary polygonal windows, marks, multiple changes, simultaneous affected-support confidence sets, and general cross-block dependence. These are research tasks, not hidden package options.
|
|
31
|
+
|
|
32
|
+
## Installation for development
|
|
33
|
+
|
|
34
|
+
Create an isolated environment and install the package from this checkout:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
python3.12 -m venv .venv
|
|
38
|
+
source .venv/bin/activate
|
|
39
|
+
python -m pip install --upgrade pip
|
|
40
|
+
python -m pip install -e ".[test,applications]"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Alternatively, create the recorded Conda environment:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
conda env create -f environment.yml
|
|
47
|
+
conda activate stireg-research
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
The package has not been released to PyPI. Do not use `pip install stireg` as a replication instruction unless an archived release is created later.
|
|
51
|
+
|
|
52
|
+
## Quick start
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
import numpy as np
|
|
56
|
+
|
|
57
|
+
from stireg import (
|
|
58
|
+
RectWindow,
|
|
59
|
+
block_pair_scores,
|
|
60
|
+
information_report,
|
|
61
|
+
regular_cells,
|
|
62
|
+
simulate_snapshot_cluster_change,
|
|
63
|
+
single_change_test,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
window = RectWindow(0, 1, 0, 1, 0, 41)
|
|
67
|
+
simulation_blocks = np.arange(0, 42)
|
|
68
|
+
analysis_blocks = np.arange(1, 42)
|
|
69
|
+
|
|
70
|
+
events = simulate_snapshot_cluster_change(
|
|
71
|
+
window,
|
|
72
|
+
block_edges=simulation_blocks,
|
|
73
|
+
change_block=21,
|
|
74
|
+
target_rate=200,
|
|
75
|
+
parent_rate_after=20,
|
|
76
|
+
cluster_sigma_after=0.02,
|
|
77
|
+
mean_offspring_after=10,
|
|
78
|
+
random_state=20260928,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
interior = window.eroded(0.15)
|
|
82
|
+
x_edges, y_edges = regular_cells(interior, nx=2, ny=2)
|
|
83
|
+
scores = block_pair_scores(
|
|
84
|
+
events,
|
|
85
|
+
block_edges=analysis_blocks,
|
|
86
|
+
x_edges=x_edges,
|
|
87
|
+
y_edges=y_edges,
|
|
88
|
+
spatial_lags=[0, 0.05, 0.10, 0.15],
|
|
89
|
+
temporal_lags=[0, 0.5, 1.0],
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
diagnostics = information_report(scores)
|
|
93
|
+
result = single_change_test(
|
|
94
|
+
scores.matrix(),
|
|
95
|
+
min_segment=8,
|
|
96
|
+
n_bootstrap=499,
|
|
97
|
+
bootstrap_block_length=1,
|
|
98
|
+
random_state=20260928,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
print(result.change_index, result.p_value)
|
|
102
|
+
print(diagnostics.n_low_information, diagnostics.n_components)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
The result reports the estimated split in units of temporal blocks, the maximum statistic, its bootstrap p-value, and the component with the largest standardized contrast. The component is diagnostic and is not a multiplicity-adjusted affected-support estimate.
|
|
106
|
+
|
|
107
|
+
## Mathematical overview
|
|
108
|
+
|
|
109
|
+
For events \(z_i=(s_i,t_i)\) with intensity \(\lambda(z_i)\), each block-cell-lag score has the form
|
|
110
|
+
|
|
111
|
+
\[
|
|
112
|
+
Y_{bp}=\frac{1}{E_{bp}}
|
|
113
|
+
\sum_{i\ne j}
|
|
114
|
+
\frac{f_{bp}(z_i,z_j)}{\lambda(z_i)\lambda(z_j)}-1,
|
|
115
|
+
\]
|
|
116
|
+
|
|
117
|
+
where \(f_{bp}\) selects one temporal block, interior spatial cell, spatial annulus, and positive temporal-lag bin. The denominator \(E_{bp}\) is an exact geometric exposure for the restricted rectangular design. The second-order Campbell formula shows that the population mean is a bin average of the pair correlation minus one, so first-order intensity cancels when the supplied intensity is correct.
|
|
118
|
+
|
|
119
|
+
The retrospective test computes standardized CUSUM contrasts over candidate split times and components, then calibrates their maximum with whole-block multipliers. Under a Poisson reference, within-block pair construction prevents event sharing across temporal blocks. It does not guarantee independence for self-exciting or Cox processes.
|
|
120
|
+
|
|
121
|
+
## API overview
|
|
122
|
+
|
|
123
|
+
- `RectWindow` and `EventPattern`: validated event data and observation geometry.
|
|
124
|
+
- `regular_cells`: regular rectangular cell boundaries.
|
|
125
|
+
- `block_pair_scores`: indexed pair enumeration, exact exposure, scores, and raw pair counts.
|
|
126
|
+
- `information_report`: transparent sparsity diagnostics based only on pair counts.
|
|
127
|
+
- `single_change_test`: one-change maximum-CUSUM test and multiplier calibration.
|
|
128
|
+
- `simulate_piecewise_poisson`: known-intensity piecewise Poisson simulation.
|
|
129
|
+
- `simulate_snapshot_cluster` and `simulate_snapshot_cluster_change`: independent clustered-snapshot null and interaction-change generators.
|
|
130
|
+
- `thin_pattern`: independent thinning with retained-process intensity updates.
|
|
131
|
+
|
|
132
|
+
All public functions include type hints, validation, and deterministic behavior when a random seed is supplied.
|
|
133
|
+
|
|
134
|
+
## Validation status
|
|
135
|
+
|
|
136
|
+
The initial 200-replicate known-intensity experiment produced rejection rates of 0.020 under stationary Poisson data and 0.025 under a pure intensity change, with detection 0.890 for a Poisson-to-cluster change. A separate 200-replicate robustness experiment produced rejection 0.045 under stationary clustered snapshots and 0.010 under an independent retention change. Detection was 0.115 at low event density and 0.920 at high density. These values describe the recorded designs only.
|
|
137
|
+
|
|
138
|
+
## Real-data acquisition
|
|
139
|
+
|
|
140
|
+
The USGS application is reproducible with:
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
python scripts/acquire_usgs_ridgecrest.py
|
|
144
|
+
python scripts/preprocess_usgs_ridgecrest.py
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
The query, retrieval time, checksums, coordinate reference systems, and attrition are recorded under `data/`. Global Fire Atlas acquisition uses the versioned Zenodo record DOI `10.5281/zenodo.11400062`. The large archive remains external to any JSS upload.
|
|
148
|
+
|
|
149
|
+
## Reproducing the article
|
|
150
|
+
|
|
151
|
+
Run unit tests first:
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
python -m pytest
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Regenerate the tests, 999-bootstrap real-data analyses, manuscript tables, and
|
|
158
|
+
figures with the single replication entry point:
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
python run_all.py
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
To rerun all four 200-replicate calibrated simulation studies before rebuilding
|
|
165
|
+
the downstream artifacts, use `python run_all.py --full`. The default mode uses
|
|
166
|
+
the committed calibrated simulation outputs so that routine replication does
|
|
167
|
+
not silently overwrite a long-running experiment.
|
|
168
|
+
|
|
169
|
+
## Citation and license
|
|
170
|
+
|
|
171
|
+
Author information, ORCID, version, and citation guidance are in `CITATION.cff`. The software is distributed under the MIT License, which is GPL-compatible for JSS submission purposes. Dataset licenses and citations remain separate and are recorded with each acquisition script.
|
stireg-0.1.0/README.md
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# stireg
|
|
2
|
+
|
|
3
|
+
`stireg` is research software for retrospective analysis of changes in localized, scale-resolved second-order interaction in spatio-temporal event patterns. It is independent of ST-SCKM and does not use a clustering objective, centroid update, or graph penalty.
|
|
4
|
+
|
|
5
|
+
The broad “interaction regime” label remains provisional. A 2026 preprint named ST-Score already addresses sequential change detection and continuous-region localization for spatio-temporal point processes. This project therefore targets a narrower estimand: changes in intensity-reweighted event-pair projections indexed by prespecified spatial cells and spatial-temporal lag bins.
|
|
6
|
+
|
|
7
|
+
## Current statistical scope
|
|
8
|
+
|
|
9
|
+
The implemented software supports one unmarked continuous event pattern in a rectangular planar window. Both events in each pair must lie in the same temporal block, cells must be inside the window eroded by the maximum spatial lag, and intensities must be known or supplied at the event locations. The change procedure tests one retrospective mean change in the complete vector of blockwise pair scores.
|
|
10
|
+
|
|
11
|
+
The following are not yet validated: automatic intensity estimation, arbitrary polygonal windows, marks, multiple changes, simultaneous affected-support confidence sets, and general cross-block dependence. These are research tasks, not hidden package options.
|
|
12
|
+
|
|
13
|
+
## Installation for development
|
|
14
|
+
|
|
15
|
+
Create an isolated environment and install the package from this checkout:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
python3.12 -m venv .venv
|
|
19
|
+
source .venv/bin/activate
|
|
20
|
+
python -m pip install --upgrade pip
|
|
21
|
+
python -m pip install -e ".[test,applications]"
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Alternatively, create the recorded Conda environment:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
conda env create -f environment.yml
|
|
28
|
+
conda activate stireg-research
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The package has not been released to PyPI. Do not use `pip install stireg` as a replication instruction unless an archived release is created later.
|
|
32
|
+
|
|
33
|
+
## Quick start
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
import numpy as np
|
|
37
|
+
|
|
38
|
+
from stireg import (
|
|
39
|
+
RectWindow,
|
|
40
|
+
block_pair_scores,
|
|
41
|
+
information_report,
|
|
42
|
+
regular_cells,
|
|
43
|
+
simulate_snapshot_cluster_change,
|
|
44
|
+
single_change_test,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
window = RectWindow(0, 1, 0, 1, 0, 41)
|
|
48
|
+
simulation_blocks = np.arange(0, 42)
|
|
49
|
+
analysis_blocks = np.arange(1, 42)
|
|
50
|
+
|
|
51
|
+
events = simulate_snapshot_cluster_change(
|
|
52
|
+
window,
|
|
53
|
+
block_edges=simulation_blocks,
|
|
54
|
+
change_block=21,
|
|
55
|
+
target_rate=200,
|
|
56
|
+
parent_rate_after=20,
|
|
57
|
+
cluster_sigma_after=0.02,
|
|
58
|
+
mean_offspring_after=10,
|
|
59
|
+
random_state=20260928,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
interior = window.eroded(0.15)
|
|
63
|
+
x_edges, y_edges = regular_cells(interior, nx=2, ny=2)
|
|
64
|
+
scores = block_pair_scores(
|
|
65
|
+
events,
|
|
66
|
+
block_edges=analysis_blocks,
|
|
67
|
+
x_edges=x_edges,
|
|
68
|
+
y_edges=y_edges,
|
|
69
|
+
spatial_lags=[0, 0.05, 0.10, 0.15],
|
|
70
|
+
temporal_lags=[0, 0.5, 1.0],
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
diagnostics = information_report(scores)
|
|
74
|
+
result = single_change_test(
|
|
75
|
+
scores.matrix(),
|
|
76
|
+
min_segment=8,
|
|
77
|
+
n_bootstrap=499,
|
|
78
|
+
bootstrap_block_length=1,
|
|
79
|
+
random_state=20260928,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
print(result.change_index, result.p_value)
|
|
83
|
+
print(diagnostics.n_low_information, diagnostics.n_components)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The result reports the estimated split in units of temporal blocks, the maximum statistic, its bootstrap p-value, and the component with the largest standardized contrast. The component is diagnostic and is not a multiplicity-adjusted affected-support estimate.
|
|
87
|
+
|
|
88
|
+
## Mathematical overview
|
|
89
|
+
|
|
90
|
+
For events \(z_i=(s_i,t_i)\) with intensity \(\lambda(z_i)\), each block-cell-lag score has the form
|
|
91
|
+
|
|
92
|
+
\[
|
|
93
|
+
Y_{bp}=\frac{1}{E_{bp}}
|
|
94
|
+
\sum_{i\ne j}
|
|
95
|
+
\frac{f_{bp}(z_i,z_j)}{\lambda(z_i)\lambda(z_j)}-1,
|
|
96
|
+
\]
|
|
97
|
+
|
|
98
|
+
where \(f_{bp}\) selects one temporal block, interior spatial cell, spatial annulus, and positive temporal-lag bin. The denominator \(E_{bp}\) is an exact geometric exposure for the restricted rectangular design. The second-order Campbell formula shows that the population mean is a bin average of the pair correlation minus one, so first-order intensity cancels when the supplied intensity is correct.
|
|
99
|
+
|
|
100
|
+
The retrospective test computes standardized CUSUM contrasts over candidate split times and components, then calibrates their maximum with whole-block multipliers. Under a Poisson reference, within-block pair construction prevents event sharing across temporal blocks. It does not guarantee independence for self-exciting or Cox processes.
|
|
101
|
+
|
|
102
|
+
## API overview
|
|
103
|
+
|
|
104
|
+
- `RectWindow` and `EventPattern`: validated event data and observation geometry.
|
|
105
|
+
- `regular_cells`: regular rectangular cell boundaries.
|
|
106
|
+
- `block_pair_scores`: indexed pair enumeration, exact exposure, scores, and raw pair counts.
|
|
107
|
+
- `information_report`: transparent sparsity diagnostics based only on pair counts.
|
|
108
|
+
- `single_change_test`: one-change maximum-CUSUM test and multiplier calibration.
|
|
109
|
+
- `simulate_piecewise_poisson`: known-intensity piecewise Poisson simulation.
|
|
110
|
+
- `simulate_snapshot_cluster` and `simulate_snapshot_cluster_change`: independent clustered-snapshot null and interaction-change generators.
|
|
111
|
+
- `thin_pattern`: independent thinning with retained-process intensity updates.
|
|
112
|
+
|
|
113
|
+
All public functions include type hints, validation, and deterministic behavior when a random seed is supplied.
|
|
114
|
+
|
|
115
|
+
## Validation status
|
|
116
|
+
|
|
117
|
+
The initial 200-replicate known-intensity experiment produced rejection rates of 0.020 under stationary Poisson data and 0.025 under a pure intensity change, with detection 0.890 for a Poisson-to-cluster change. A separate 200-replicate robustness experiment produced rejection 0.045 under stationary clustered snapshots and 0.010 under an independent retention change. Detection was 0.115 at low event density and 0.920 at high density. These values describe the recorded designs only.
|
|
118
|
+
|
|
119
|
+
## Real-data acquisition
|
|
120
|
+
|
|
121
|
+
The USGS application is reproducible with:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
python scripts/acquire_usgs_ridgecrest.py
|
|
125
|
+
python scripts/preprocess_usgs_ridgecrest.py
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
The query, retrieval time, checksums, coordinate reference systems, and attrition are recorded under `data/`. Global Fire Atlas acquisition uses the versioned Zenodo record DOI `10.5281/zenodo.11400062`. The large archive remains external to any JSS upload.
|
|
129
|
+
|
|
130
|
+
## Reproducing the article
|
|
131
|
+
|
|
132
|
+
Run unit tests first:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
python -m pytest
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Regenerate the tests, 999-bootstrap real-data analyses, manuscript tables, and
|
|
139
|
+
figures with the single replication entry point:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
python run_all.py
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
To rerun all four 200-replicate calibrated simulation studies before rebuilding
|
|
146
|
+
the downstream artifacts, use `python run_all.py --full`. The default mode uses
|
|
147
|
+
the committed calibrated simulation outputs so that routine replication does
|
|
148
|
+
not silently overwrite a long-running experiment.
|
|
149
|
+
|
|
150
|
+
## Citation and license
|
|
151
|
+
|
|
152
|
+
Author information, ORCID, version, and citation guidance are in `CITATION.cff`. The software is distributed under the MIT License, which is GPL-compatible for JSS submission purposes. Dataset licenses and citations remain separate and are recorded with each acquisition script.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Executable version of the complete worked example in the JSS manuscript."""
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
from stireg import RectWindow, block_pair_scores, information_report
|
|
6
|
+
from stireg import regular_cells, simulate_snapshot_cluster_change
|
|
7
|
+
from stireg import single_change_test
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
window = RectWindow(0, 1, 0, 1, 0, 41)
|
|
11
|
+
events = simulate_snapshot_cluster_change(
|
|
12
|
+
window,
|
|
13
|
+
block_edges=np.arange(0, 42),
|
|
14
|
+
change_block=21,
|
|
15
|
+
target_rate=200,
|
|
16
|
+
parent_rate_after=20,
|
|
17
|
+
cluster_sigma_after=0.02,
|
|
18
|
+
mean_offspring_after=10,
|
|
19
|
+
random_state=20260928,
|
|
20
|
+
)
|
|
21
|
+
interior = window.eroded(0.15)
|
|
22
|
+
x_edges, y_edges = regular_cells(interior, nx=2, ny=2)
|
|
23
|
+
cube = block_pair_scores(
|
|
24
|
+
events,
|
|
25
|
+
block_edges=np.arange(1, 42),
|
|
26
|
+
x_edges=x_edges,
|
|
27
|
+
y_edges=y_edges,
|
|
28
|
+
spatial_lags=[0, 0.05, 0.10, 0.15],
|
|
29
|
+
temporal_lags=[0, 0.5, 1.0],
|
|
30
|
+
)
|
|
31
|
+
diagnostic = information_report(cube)
|
|
32
|
+
fit = single_change_test(
|
|
33
|
+
cube.matrix(),
|
|
34
|
+
min_segment=8,
|
|
35
|
+
n_bootstrap=499,
|
|
36
|
+
bootstrap_block_length=1,
|
|
37
|
+
random_state=20260928,
|
|
38
|
+
)
|
|
39
|
+
print(cube.values.shape, diagnostic.n_low_information)
|
|
40
|
+
print(fit.change_index, fit.component_index, fit.p_value)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.25"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "stireg"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Scale-resolved pair-interaction change detection for spatio-temporal point patterns"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = {text = "MIT"}
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "Muh Akbar Idris"}
|
|
14
|
+
]
|
|
15
|
+
dependencies = [
|
|
16
|
+
"numpy>=1.24",
|
|
17
|
+
"scipy>=1.10"
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
[project.optional-dependencies]
|
|
21
|
+
test = ["pytest>=8", "pytest-cov>=5"]
|
|
22
|
+
applications = ["pandas>=2.0", "pyproj>=3.6", "pyshp>=2.3"]
|
|
23
|
+
|
|
24
|
+
[tool.hatch.build.targets.wheel]
|
|
25
|
+
packages = ["src/stireg"]
|
|
26
|
+
|
|
27
|
+
[tool.hatch.build.targets.sdist]
|
|
28
|
+
include = [
|
|
29
|
+
"/src",
|
|
30
|
+
"/tests",
|
|
31
|
+
"/examples",
|
|
32
|
+
"/README.md",
|
|
33
|
+
"/LICENSE",
|
|
34
|
+
"/CITATION.cff",
|
|
35
|
+
"/CHANGELOG.md",
|
|
36
|
+
"/requirements.txt",
|
|
37
|
+
"/pyproject.toml",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
[tool.pytest.ini_options]
|
|
41
|
+
addopts = "-ra"
|
|
42
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Research implementation of localized interaction-change inference."""
|
|
2
|
+
|
|
3
|
+
from .change import SingleChangeResult, single_change_test
|
|
4
|
+
from .data import EventPattern, RectWindow, regular_cells
|
|
5
|
+
from .diagnostics import InformationReport, information_report
|
|
6
|
+
from .interaction import ScoreCube, block_pair_scores
|
|
7
|
+
from .intensity import (
|
|
8
|
+
ConditionalIntensityEstimate,
|
|
9
|
+
conditional_histogram_intensity,
|
|
10
|
+
conditional_spatial_histogram_intensity,
|
|
11
|
+
)
|
|
12
|
+
from .simulation import (
|
|
13
|
+
simulate_piecewise_poisson,
|
|
14
|
+
simulate_piecewise_two_zone_poisson,
|
|
15
|
+
simulate_local_cluster_change,
|
|
16
|
+
simulate_snapshot_cluster,
|
|
17
|
+
simulate_snapshot_cluster_change,
|
|
18
|
+
thin_pattern,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"EventPattern",
|
|
23
|
+
"ConditionalIntensityEstimate",
|
|
24
|
+
"InformationReport",
|
|
25
|
+
"RectWindow",
|
|
26
|
+
"ScoreCube",
|
|
27
|
+
"SingleChangeResult",
|
|
28
|
+
"block_pair_scores",
|
|
29
|
+
"conditional_spatial_histogram_intensity",
|
|
30
|
+
"conditional_histogram_intensity",
|
|
31
|
+
"information_report",
|
|
32
|
+
"regular_cells",
|
|
33
|
+
"simulate_piecewise_poisson",
|
|
34
|
+
"simulate_piecewise_two_zone_poisson",
|
|
35
|
+
"simulate_local_cluster_change",
|
|
36
|
+
"simulate_snapshot_cluster",
|
|
37
|
+
"simulate_snapshot_cluster_change",
|
|
38
|
+
"single_change_test",
|
|
39
|
+
"thin_pattern",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""Single-change inference for blockwise interaction scores."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
from numpy.typing import ArrayLike, NDArray
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
FloatArray = NDArray[np.float64]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(frozen=True)
|
|
15
|
+
class SingleChangeResult:
|
|
16
|
+
statistic: float
|
|
17
|
+
p_value: float
|
|
18
|
+
change_index: int
|
|
19
|
+
component_index: int
|
|
20
|
+
component_statistics: FloatArray
|
|
21
|
+
bootstrap_statistics: FloatArray
|
|
22
|
+
n_blocks: int
|
|
23
|
+
min_segment: int
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _cusum_tensor(x: FloatArray, min_segment: int, scale: FloatArray) -> tuple[FloatArray, NDArray[np.int64]]:
|
|
27
|
+
n, _ = x.shape
|
|
28
|
+
candidates = np.arange(min_segment, n - min_segment + 1, dtype=np.int64)
|
|
29
|
+
prefix = np.vstack([np.zeros((1, x.shape[1])), np.cumsum(x, axis=0)])
|
|
30
|
+
left = prefix[candidates] / candidates[:, None]
|
|
31
|
+
right = (prefix[n] - prefix[candidates]) / (n - candidates)[:, None]
|
|
32
|
+
factor = np.sqrt(candidates * (n - candidates) / n)[:, None]
|
|
33
|
+
return factor * (left - right) / scale[None, :], candidates
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _dependent_multipliers(n: int, block_length: int, rng: np.random.Generator) -> FloatArray:
|
|
37
|
+
noise = rng.normal(size=n + block_length - 1)
|
|
38
|
+
kernel = np.ones(block_length) / np.sqrt(block_length)
|
|
39
|
+
multipliers = np.convolve(noise, kernel, mode="valid")
|
|
40
|
+
multipliers -= multipliers.mean()
|
|
41
|
+
sd = multipliers.std(ddof=1)
|
|
42
|
+
return multipliers / sd if sd > 0 else multipliers
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def single_change_test(
|
|
46
|
+
scores: ArrayLike,
|
|
47
|
+
*,
|
|
48
|
+
min_segment: int = 4,
|
|
49
|
+
n_bootstrap: int = 499,
|
|
50
|
+
bootstrap_block_length: int = 2,
|
|
51
|
+
random_state: int | np.random.Generator | None = None,
|
|
52
|
+
) -> SingleChangeResult:
|
|
53
|
+
"""Test for one change in a block-by-component score matrix.
|
|
54
|
+
|
|
55
|
+
A dependent multiplier bootstrap calibrates the maximum over candidate
|
|
56
|
+
times and components. The returned change index is the number of blocks on
|
|
57
|
+
the left side of the estimated split.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
x = np.asarray(scores, dtype=float)
|
|
61
|
+
if x.ndim != 2 or x.shape[1] < 1:
|
|
62
|
+
raise ValueError("scores must be a two-dimensional block-by-component array")
|
|
63
|
+
if not np.all(np.isfinite(x)):
|
|
64
|
+
raise ValueError("scores must contain only finite values")
|
|
65
|
+
n, _ = x.shape
|
|
66
|
+
if not isinstance(min_segment, int) or min_segment < 2 or 2 * min_segment > n:
|
|
67
|
+
raise ValueError("min_segment must be at least 2 and fit twice within the series")
|
|
68
|
+
if not isinstance(n_bootstrap, int) or n_bootstrap < 19:
|
|
69
|
+
raise ValueError("n_bootstrap must be an integer of at least 19")
|
|
70
|
+
if not isinstance(bootstrap_block_length, int) or not 1 <= bootstrap_block_length <= n:
|
|
71
|
+
raise ValueError("bootstrap_block_length must be between 1 and the number of blocks")
|
|
72
|
+
|
|
73
|
+
scale = x.std(axis=0, ddof=1)
|
|
74
|
+
informative = scale > np.sqrt(np.finfo(float).eps)
|
|
75
|
+
if not np.any(informative):
|
|
76
|
+
raise ValueError("at least one score component must vary over time")
|
|
77
|
+
x_use = x[:, informative]
|
|
78
|
+
scale_use = scale[informative]
|
|
79
|
+
observed, candidates = _cusum_tensor(x_use, min_segment, scale_use)
|
|
80
|
+
abs_observed = np.abs(observed)
|
|
81
|
+
flat_index = int(np.argmax(abs_observed))
|
|
82
|
+
time_pos, component_pos = np.unravel_index(flat_index, abs_observed.shape)
|
|
83
|
+
statistic = float(abs_observed[time_pos, component_pos])
|
|
84
|
+
|
|
85
|
+
rng = random_state if isinstance(random_state, np.random.Generator) else np.random.default_rng(random_state)
|
|
86
|
+
centered = x_use - x_use.mean(axis=0, keepdims=True)
|
|
87
|
+
boot = np.empty(n_bootstrap, dtype=float)
|
|
88
|
+
for b in range(n_bootstrap):
|
|
89
|
+
xi = _dependent_multipliers(n, bootstrap_block_length, rng)
|
|
90
|
+
sample = centered * xi[:, None]
|
|
91
|
+
boot_cusum, _ = _cusum_tensor(sample, min_segment, scale_use)
|
|
92
|
+
boot[b] = np.max(np.abs(boot_cusum))
|
|
93
|
+
p_value = (1.0 + np.count_nonzero(boot >= statistic)) / (n_bootstrap + 1.0)
|
|
94
|
+
original_components = np.flatnonzero(informative)
|
|
95
|
+
return SingleChangeResult(
|
|
96
|
+
statistic=statistic,
|
|
97
|
+
p_value=float(p_value),
|
|
98
|
+
change_index=int(candidates[time_pos]),
|
|
99
|
+
component_index=int(original_components[component_pos]),
|
|
100
|
+
component_statistics=observed[time_pos],
|
|
101
|
+
bootstrap_statistics=boot,
|
|
102
|
+
n_blocks=n,
|
|
103
|
+
min_segment=min_segment,
|
|
104
|
+
)
|
|
105
|
+
|