views-baseline 1.0.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.
Files changed (37) hide show
  1. views_baseline-1.0.0/.gitignore +218 -0
  2. views_baseline-1.0.0/LICENSE +21 -0
  3. views_baseline-1.0.0/PKG-INFO +207 -0
  4. views_baseline-1.0.0/README.md +184 -0
  5. views_baseline-1.0.0/pyproject.toml +63 -0
  6. views_baseline-1.0.0/views_baseline/__init__.py +0 -0
  7. views_baseline-1.0.0/views_baseline/evaluation/__init__.py +0 -0
  8. views_baseline-1.0.0/views_baseline/evaluation/closeness.py +152 -0
  9. views_baseline-1.0.0/views_baseline/infrastructure/__init__.py +0 -0
  10. views_baseline-1.0.0/views_baseline/infrastructure/exceptions.py +10 -0
  11. views_baseline-1.0.0/views_baseline/infrastructure/reproducibility_gate.py +112 -0
  12. views_baseline-1.0.0/views_baseline/manager/__init__.py +0 -0
  13. views_baseline-1.0.0/views_baseline/manager/baseline_manager.py +152 -0
  14. views_baseline-1.0.0/views_baseline/model/__init__.py +1 -0
  15. views_baseline-1.0.0/views_baseline/model/catalog.py +120 -0
  16. views_baseline-1.0.0/views_baseline/model/defaults.py +9 -0
  17. views_baseline-1.0.0/views_baseline/model/distributions/__init__.py +64 -0
  18. views_baseline-1.0.0/views_baseline/model/distributions/families.py +182 -0
  19. views_baseline-1.0.0/views_baseline/model/distributions/registry.py +39 -0
  20. views_baseline-1.0.0/views_baseline/model/distributions/transforms.py +61 -0
  21. views_baseline-1.0.0/views_baseline/model/frames/__init__.py +0 -0
  22. views_baseline-1.0.0/views_baseline/model/frames/input.py +246 -0
  23. views_baseline-1.0.0/views_baseline/model/frames/output.py +144 -0
  24. views_baseline-1.0.0/views_baseline/model/frames/pooling.py +148 -0
  25. views_baseline-1.0.0/views_baseline/model/grid.py +90 -0
  26. views_baseline-1.0.0/views_baseline/model/models/__init__.py +0 -0
  27. views_baseline-1.0.0/views_baseline/model/models/distributional/__init__.py +13 -0
  28. views_baseline-1.0.0/views_baseline/model/models/distributional/conflictology.py +74 -0
  29. views_baseline-1.0.0/views_baseline/model/models/distributional/mixture.py +104 -0
  30. views_baseline-1.0.0/views_baseline/model/models/distributional/parametric.py +95 -0
  31. views_baseline-1.0.0/views_baseline/model/models/distributional/parametric_hurdle.py +115 -0
  32. views_baseline-1.0.0/views_baseline/model/models/point/__init__.py +5 -0
  33. views_baseline-1.0.0/views_baseline/model/models/point/average.py +78 -0
  34. views_baseline-1.0.0/views_baseline/model/models/point/locf.py +75 -0
  35. views_baseline-1.0.0/views_baseline/model/models/point/zero.py +63 -0
  36. views_baseline-1.0.0/views_baseline/model/protocol.py +44 -0
  37. views_baseline-1.0.0/views_baseline/model/spatial.py +72 -0
@@ -0,0 +1,218 @@
1
+ # ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
2
+ # :: PLEASE DO NOT DELETE THIS FILE OR ANY OF ITS CONTENTS WITHOUT PERMISSION FROM THE MD&D TEAM ::
3
+ # ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
4
+
5
+ # Adding new file types to the ignore list is less controversial than removing them,
6
+ # But please, take a second to consult with the team before doing so anyways.
7
+
8
+
9
+ # Byte-compiled / optimized / DLL files
10
+ __pycache__/
11
+ *.py[cod]
12
+ *$py.class
13
+
14
+ # C extensions
15
+ *.so
16
+
17
+ # Distribution / packaging
18
+ .Python
19
+ build/
20
+ develop-eggs/
21
+ dist/
22
+ downloads/
23
+ eggs/
24
+ .eggs/
25
+ lib/
26
+ lib64/
27
+ parts/
28
+ sdist/
29
+ var/
30
+ wheels/
31
+ share/python-wheels/
32
+ *.egg-info/
33
+ .installed.cfg
34
+ *.egg
35
+ MANIFEST
36
+
37
+ # PyInstaller
38
+ # Usually these files are written by a python script from a template
39
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
40
+ *.manifest
41
+ *.spec
42
+
43
+ # Installer logs
44
+ pip-log.txt
45
+ pip-delete-this-directory.txt
46
+
47
+ # Unit test / coverage reports
48
+ htmlcov/
49
+ .tox/
50
+ .nox/
51
+ .coverage
52
+ .coverage.*
53
+ .cache
54
+ nosetests.xml
55
+ coverage.xml
56
+ *.cover
57
+ *.py,cover
58
+ .hypothesis/
59
+ .pytest_cache/
60
+ cover/
61
+
62
+ # Translations
63
+ *.mo
64
+ *.pot
65
+
66
+ # Django stuff:
67
+ *.log
68
+ local_settings.py
69
+ db.sqlite3
70
+ db.sqlite3-journal
71
+
72
+ # Flask stuff:
73
+ instance/
74
+ .webassets-cache
75
+
76
+ # Scrapy stuff:
77
+ .scrapy
78
+
79
+ # Sphinx documentation
80
+ docs/_build/
81
+
82
+ # PyBuilder
83
+ .pybuilder/
84
+ target/
85
+
86
+ # Jupyter Notebook
87
+ .ipynb_checkpoints
88
+
89
+ # IPython
90
+ profile_default/
91
+ ipython_config.py
92
+
93
+ # pyenv
94
+ # For a library or package, you might want to ignore these files since the code is
95
+ # intended to run in multiple environments; otherwise, check them in:
96
+ # .python-version
97
+
98
+ # pipenv
99
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
100
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
101
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
102
+ # install all needed dependencies.
103
+ #Pipfile.lock
104
+
105
+ # poetry
106
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
107
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
108
+ # commonly ignored for libraries.
109
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
110
+ #poetry.lock
111
+
112
+ # pdm
113
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
114
+ #pdm.lock
115
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
116
+ # in version control.
117
+ # https://pdm.fming.dev/#use-with-ide
118
+ .pdm.toml
119
+
120
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
121
+ __pypackages__/
122
+
123
+ # Celery stuff
124
+ celerybeat-schedule
125
+ celerybeat.pid
126
+
127
+ # SageMath parsed files
128
+ *.sage.py
129
+
130
+ # Environments
131
+ .env
132
+ .venv
133
+ env/
134
+ venv/
135
+ ENV/
136
+ env.bak/
137
+ venv.bak/
138
+
139
+ # Spyder project settings
140
+ .spyderproject
141
+ .spyproject
142
+
143
+ # Rope project settings
144
+ .ropeproject
145
+
146
+ # mkdocs documentation
147
+ /site
148
+
149
+ # Global cache
150
+ .global_cache.pkl
151
+
152
+ # mypy
153
+ .mypy_cache/
154
+ .dmypy.json
155
+ dmypy.json
156
+
157
+ # Pyre type checker
158
+ .pyre/
159
+
160
+ # pytype static type analyzer
161
+ .pytype/
162
+
163
+ # Cython debug symbols
164
+ cython_debug/
165
+
166
+ # PyCharm
167
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
168
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
169
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
170
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
171
+ #.idea/
172
+
173
+ # media files
174
+ *.esp
175
+ *.tiff
176
+ *.pyc
177
+ *.zip
178
+ *.JPG
179
+ *.jpg
180
+ *.mp4
181
+ *.png
182
+ *.svg
183
+ *.jpeg
184
+ *.gif
185
+
186
+ # presentation files
187
+ *.pptx
188
+ *.ppt
189
+ *.pdf
190
+ *.html
191
+
192
+ # data files
193
+ *.csv
194
+ *.npy
195
+ *.parquet
196
+ *.pt
197
+ *.pkl
198
+
199
+ # folders
200
+ **/wandb/
201
+ **/notebooks/
202
+
203
+ # mac specific
204
+ .DS_Store
205
+
206
+ # vscode
207
+ .idea/
208
+ .vscode
209
+
210
+ # backup files
211
+ *.bak
212
+
213
+ # txt logs
214
+ *.txt
215
+ reports/
216
+
217
+ # graphify generated output
218
+ graphify-out/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Sonja Häffner and Simon Polichinel von der Maase
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.
@@ -0,0 +1,207 @@
1
+ Metadata-Version: 2.4
2
+ Name: views-baseline
3
+ Version: 1.0.0
4
+ Summary: VIEWS conflict-forecasting baseline models — point (Zero/LOCF/Average) and distributional (conflictology, mixture, parametric) baselines that emit views-frames PredictionFrames.
5
+ Project-URL: Homepage, https://github.com/views-platform/views-baseline
6
+ Project-URL: Repository, https://github.com/views-platform/views-baseline
7
+ Project-URL: Changelog, https://github.com/views-platform/views-baseline/releases
8
+ Author-email: Sonja Häffner <sonhaf@prio.org>, Simon Polichinel von der Maase <simmaa@prio.org>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Classifier: Development Status :: 5 - Production/Stable
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: Scientific/Engineering
19
+ Requires-Python: <3.14,>=3.11
20
+ Requires-Dist: views-frames<2.0.0,>=1.3.0
21
+ Requires-Dist: views-pipeline-core<4.0.0,>=3.0.0
22
+ Description-Content-Type: text/markdown
23
+
24
+ # views-baseline
25
+
26
+ Baseline forecasting models for the VIEWS pipeline.
27
+
28
+ This package provides **simple, transparent baseline models** that can be used for benchmarking more complex forecasting models and sanity checks. The baselines are intentionally minimal and deterministic (or reproducibly stochastic for distributional models).
29
+
30
+ ---
31
+
32
+ ## Overview
33
+
34
+ The package implements several common baseline strategies for **panel time-series data** with a multi-index of:
35
+
36
+ * **time** (e.g. `month_id`)
37
+ * **entity** (e.g. `priogrid_id`, `country_id`)
38
+
39
+ All models follow a common interface:
40
+
41
+ ```python
42
+ model.fit(df) # df: a pandas DataFrame OR a views_frames FeatureFrame
43
+ predictions = model.predict(df, sequence_number)
44
+ ```
45
+
46
+ `fit()` / `predict()` accept **either** a pandas `DataFrame` **or** a `views_frames.FeatureFrame` (ADR-019): the input is normalized to a `FeatureFrame` once at the boundary (`to_feature_frame` in `model/frames/input.py`, the only place pandas is read), and all windowing/aggregation runs on its numpy arrays. Models are fitted automatically via `fit()` before generating predictions. Fitted model artifacts are pickled for ensemble compatibility.
47
+
48
+ ---
49
+
50
+ ## Implemented Models
51
+
52
+ All baselines operate on panel data indexed by **(time `t`, unit `i`)** — `month_id` × spatial unit (`priogrid_id` at `pgm`, `country_id` at `cm`) — and forecast one or more target columns over a horizon of `output_length` steps starting at `test_start + sequence_number`. ("unit" is the term used in a `PredictionFrame`'s `SpatioTemporalIndex`; the input index calls the same axis the *entity*.)
53
+
54
+ **Causal split (no leakage).** Every model is fit using only observations strictly before the test period. The last training month is `train_end = test_start − 1`; no value at or after `test_start` enters any fitted quantity. (The point models and `MixtureBaseline` filter `t < test_start`; `ConflictologyModel` filters `t ≤ train_end` — the same boundary, written two ways.)
55
+
56
+ Two output families:
57
+
58
+ - **Point forecasts** — one deterministic value per (unit, time, target). Returned as `dict[str, PredictionFrame]` with `y_pred` of shape `(N, 1)`.
59
+ - **Distributional forecasts** — `n_samples` Monte-Carlo draws per (unit, time, target). Returned as `dict[str, PredictionFrame]` with `y_pred` of shape `(N, n_samples)`.
60
+
61
+ In all cases `N = (number of units) × output_length`, and each `PredictionFrame` carries a `SpatioTemporalIndex` with the `time`, `unit`, and spatial `level` (CM/PGM) of every row. All frames are built through a single construction seam, `to_prediction_frames` in `model/frames/output.py` (ADR-020), and the `level` is derived from the declared `loa` and validated against the input index (ADR-003).
62
+
63
+ ### Point Forecast Models
64
+
65
+ #### ZeroModel
66
+
67
+ Predicts exactly **0** for every target, unit, and forecast step. The lower-bound reference; performs no fitting.
68
+
69
+ ```python
70
+ ZeroModel(targets, partition_dict, loa)
71
+ ```
72
+
73
+ #### LocfModel — Last Observation Carried Forward
74
+
75
+ For each unit and target, carries the **last observed value at `train_end`** forward unchanged across the entire horizon. A persistence baseline ("the most recent observation is the best guess"), strong for highly autocorrelated targets.
76
+
77
+ ```python
78
+ LocfModel(targets, partition_dict, loa)
79
+ ```
80
+
81
+ #### AverageModel
82
+
83
+ For each unit and target, forecasts the **arithmetic mean of that unit's last `window_months` observations** before `test_start`, held constant across the horizon. A smoothed-persistence baseline, more robust than LOCF when individual months are noisy.
84
+
85
+ ```python
86
+ AverageModel(targets, window_months, partition_dict, loa)
87
+ ```
88
+
89
+ * Means are computed per unit; units with no history before `test_start` are skipped (logged).
90
+
91
+ ### Distributional Models
92
+
93
+ Both draw `n_samples` i.i.d. samples per cell from a fresh, seeded generator (`numpy.random.default_rng(seed)`), so a given (data, configuration, seed) reproduces bit-for-bit. The RNG is consumed in a fixed unit → time → target order to guarantee reproducibility.
94
+
95
+ #### ConflictologyModel — empirical climatology
96
+
97
+ For each unit `i`, collects that unit's **last `window_months` observed values** up to `train_end`, then draws `n_samples` samples **with replacement** from that per-unit history for every forecast cell. The predictive distribution for a cell is the recent empirical distribution of that same unit — a conflict "climatology." It uses only the unit's own recent history: no pooling across units, no older history.
98
+
99
+ ```python
100
+ ConflictologyModel(targets, window_months, partition_dict, loa, n_samples, seed=42)
101
+ ```
102
+
103
+ #### MixtureBaseline — mixture of local and global empirical pools
104
+
105
+ Combines two empirical sources to avoid the **zero-probability trap** (a unit whose recent history is entirely zero being structurally unable to predict a nonzero outcome):
106
+
107
+ - **Local pool** — the unit's last `window_months` observed values (as in `ConflictologyModel`).
108
+ - **Global pool** — **all strictly-positive** observed values, pooled across **every unit** and the **entire training span**.
109
+
110
+ Each of the `n_samples` draws is taken from the **global** pool with probability `lambda_mix`, otherwise from the **local** pool (probability `1 − lambda_mix`). At `lambda_mix = 0` it reduces to a local-only empirical baseline (same source as `ConflictologyModel`); larger `lambda_mix` injects more cross-unit, full-history positive mass.
111
+
112
+ ```python
113
+ MixtureBaseline(targets, window_months, lambda_mix, n_samples, partition_dict, loa, seed=42)
114
+ ```
115
+
116
+ #### ParametricConflictology — no-hurdle parametric climatology
117
+
118
+ The parametric counterpart of `ConflictologyModel`: instead of resampling each unit's window empirically, it **fits a single native-zero distribution** (`family`, e.g. `nb`) to that same window and draws `n_samples` per cell from the fitted law. `family`, `transform`, and `seed` are required, audited genome keys (ADR-021/ADR-022); `transform="log1p"` is illegal for count families and fails loud. In the closeness study (see `reports/closeness_experiment/`), `nb` is the family closest to conflictology on the C2ST indistinguishability metric.
119
+
120
+ ```python
121
+ ParametricConflictology(targets, window_months, partition_dict, loa, n_samples, family, transform="none", seed=42)
122
+ ```
123
+
124
+ #### ParametricHurdleConflictology — hurdle parametric climatology
125
+
126
+ A two-part law per unit: a **zero-spike** (empirical zero-rate, Bernoulli) plus a **continuous positive-part family** (`lognormal`/`gumbel`/`gamma`) fit to the positive window values (mirrors Vesco et al. 2026's RVI mixture). `transform` (`none`/`log1p`) applies to the positive part and is inverted **per sample**; a non-negativity floor (`EMIT_FLOOR`) guarantees no negative magnitudes even for `gumbel`. Closeness study: `gamma`/`none` is closest on magnitude fidelity (Wasserstein/energy); `log1p` is worse on active cells. (Tweedie was evaluated and excluded — ADR-022.)
127
+
128
+ ```python
129
+ ParametricHurdleConflictology(targets, window_months, partition_dict, loa, n_samples, family, transform="none", seed=42)
130
+ ```
131
+
132
+ ---
133
+
134
+ ## Model Catalog
135
+
136
+ The `BaselineModelCatalog` provides a factory for instantiating models based on config:
137
+
138
+ ```python
139
+ from views_baseline.model.catalog import BaselineModelCatalog
140
+
141
+ catalog = BaselineModelCatalog(config, partition_dict, loa)
142
+ model = catalog.get_model("LocfModel")
143
+ ```
144
+
145
+ Available models:
146
+
147
+ ```python
148
+ catalog.list_models()
149
+ # ['ZeroModel', 'LocfModel', 'AverageModel', 'ConflictologyModel', 'MixtureBaseline',
150
+ # 'ParametricConflictology', 'ParametricHurdleConflictology']
151
+ ```
152
+
153
+ ---
154
+
155
+ ## Integration with the VIEWS Pipeline
156
+
157
+ The package integrates with the core pipeline via:
158
+
159
+ ```python
160
+ BaselineForecastingModelManager
161
+ ```
162
+
163
+ Key characteristics:
164
+
165
+ * Models are fitted automatically via `fit()` and artifacts are pickled
166
+ * Predictions are generated per evaluation sequence
167
+ * Supports both evaluation and forecasting modes
168
+ * Distributional models are dispatched automatically via the `DistributionalBaselineModel` protocol
169
+
170
+ ---
171
+
172
+ ## Data Assumptions
173
+
174
+ Input DataFrame:
175
+
176
+ * Must be indexed by `(time, entity)` as a MultiIndex
177
+ * Must contain target columns specified in `config['targets']`
178
+
179
+ Example index:
180
+
181
+ ```text
182
+ MultiIndex(levels=[month_id, priogrid_id])
183
+ ```
184
+
185
+ ---
186
+
187
+ ## Notes & Caveats
188
+
189
+ * Entities without sufficient history are skipped (with a warning)
190
+ * No imputation beyond what the baseline logic implies
191
+ * No clipping or post-processing is applied by default
192
+
193
+ ---
194
+
195
+ ## Installation
196
+
197
+ Clone the repository and install with pip:
198
+
199
+ ```bash
200
+ pip install -e .
201
+ ```
202
+
203
+ ---
204
+
205
+ ## License / Usage
206
+
207
+ Internal VIEWS package. Intended for research and forecasting pipelines, not as a general-purpose forecasting library.
@@ -0,0 +1,184 @@
1
+ # views-baseline
2
+
3
+ Baseline forecasting models for the VIEWS pipeline.
4
+
5
+ This package provides **simple, transparent baseline models** that can be used for benchmarking more complex forecasting models and sanity checks. The baselines are intentionally minimal and deterministic (or reproducibly stochastic for distributional models).
6
+
7
+ ---
8
+
9
+ ## Overview
10
+
11
+ The package implements several common baseline strategies for **panel time-series data** with a multi-index of:
12
+
13
+ * **time** (e.g. `month_id`)
14
+ * **entity** (e.g. `priogrid_id`, `country_id`)
15
+
16
+ All models follow a common interface:
17
+
18
+ ```python
19
+ model.fit(df) # df: a pandas DataFrame OR a views_frames FeatureFrame
20
+ predictions = model.predict(df, sequence_number)
21
+ ```
22
+
23
+ `fit()` / `predict()` accept **either** a pandas `DataFrame` **or** a `views_frames.FeatureFrame` (ADR-019): the input is normalized to a `FeatureFrame` once at the boundary (`to_feature_frame` in `model/frames/input.py`, the only place pandas is read), and all windowing/aggregation runs on its numpy arrays. Models are fitted automatically via `fit()` before generating predictions. Fitted model artifacts are pickled for ensemble compatibility.
24
+
25
+ ---
26
+
27
+ ## Implemented Models
28
+
29
+ All baselines operate on panel data indexed by **(time `t`, unit `i`)** — `month_id` × spatial unit (`priogrid_id` at `pgm`, `country_id` at `cm`) — and forecast one or more target columns over a horizon of `output_length` steps starting at `test_start + sequence_number`. ("unit" is the term used in a `PredictionFrame`'s `SpatioTemporalIndex`; the input index calls the same axis the *entity*.)
30
+
31
+ **Causal split (no leakage).** Every model is fit using only observations strictly before the test period. The last training month is `train_end = test_start − 1`; no value at or after `test_start` enters any fitted quantity. (The point models and `MixtureBaseline` filter `t < test_start`; `ConflictologyModel` filters `t ≤ train_end` — the same boundary, written two ways.)
32
+
33
+ Two output families:
34
+
35
+ - **Point forecasts** — one deterministic value per (unit, time, target). Returned as `dict[str, PredictionFrame]` with `y_pred` of shape `(N, 1)`.
36
+ - **Distributional forecasts** — `n_samples` Monte-Carlo draws per (unit, time, target). Returned as `dict[str, PredictionFrame]` with `y_pred` of shape `(N, n_samples)`.
37
+
38
+ In all cases `N = (number of units) × output_length`, and each `PredictionFrame` carries a `SpatioTemporalIndex` with the `time`, `unit`, and spatial `level` (CM/PGM) of every row. All frames are built through a single construction seam, `to_prediction_frames` in `model/frames/output.py` (ADR-020), and the `level` is derived from the declared `loa` and validated against the input index (ADR-003).
39
+
40
+ ### Point Forecast Models
41
+
42
+ #### ZeroModel
43
+
44
+ Predicts exactly **0** for every target, unit, and forecast step. The lower-bound reference; performs no fitting.
45
+
46
+ ```python
47
+ ZeroModel(targets, partition_dict, loa)
48
+ ```
49
+
50
+ #### LocfModel — Last Observation Carried Forward
51
+
52
+ For each unit and target, carries the **last observed value at `train_end`** forward unchanged across the entire horizon. A persistence baseline ("the most recent observation is the best guess"), strong for highly autocorrelated targets.
53
+
54
+ ```python
55
+ LocfModel(targets, partition_dict, loa)
56
+ ```
57
+
58
+ #### AverageModel
59
+
60
+ For each unit and target, forecasts the **arithmetic mean of that unit's last `window_months` observations** before `test_start`, held constant across the horizon. A smoothed-persistence baseline, more robust than LOCF when individual months are noisy.
61
+
62
+ ```python
63
+ AverageModel(targets, window_months, partition_dict, loa)
64
+ ```
65
+
66
+ * Means are computed per unit; units with no history before `test_start` are skipped (logged).
67
+
68
+ ### Distributional Models
69
+
70
+ Both draw `n_samples` i.i.d. samples per cell from a fresh, seeded generator (`numpy.random.default_rng(seed)`), so a given (data, configuration, seed) reproduces bit-for-bit. The RNG is consumed in a fixed unit → time → target order to guarantee reproducibility.
71
+
72
+ #### ConflictologyModel — empirical climatology
73
+
74
+ For each unit `i`, collects that unit's **last `window_months` observed values** up to `train_end`, then draws `n_samples` samples **with replacement** from that per-unit history for every forecast cell. The predictive distribution for a cell is the recent empirical distribution of that same unit — a conflict "climatology." It uses only the unit's own recent history: no pooling across units, no older history.
75
+
76
+ ```python
77
+ ConflictologyModel(targets, window_months, partition_dict, loa, n_samples, seed=42)
78
+ ```
79
+
80
+ #### MixtureBaseline — mixture of local and global empirical pools
81
+
82
+ Combines two empirical sources to avoid the **zero-probability trap** (a unit whose recent history is entirely zero being structurally unable to predict a nonzero outcome):
83
+
84
+ - **Local pool** — the unit's last `window_months` observed values (as in `ConflictologyModel`).
85
+ - **Global pool** — **all strictly-positive** observed values, pooled across **every unit** and the **entire training span**.
86
+
87
+ Each of the `n_samples` draws is taken from the **global** pool with probability `lambda_mix`, otherwise from the **local** pool (probability `1 − lambda_mix`). At `lambda_mix = 0` it reduces to a local-only empirical baseline (same source as `ConflictologyModel`); larger `lambda_mix` injects more cross-unit, full-history positive mass.
88
+
89
+ ```python
90
+ MixtureBaseline(targets, window_months, lambda_mix, n_samples, partition_dict, loa, seed=42)
91
+ ```
92
+
93
+ #### ParametricConflictology — no-hurdle parametric climatology
94
+
95
+ The parametric counterpart of `ConflictologyModel`: instead of resampling each unit's window empirically, it **fits a single native-zero distribution** (`family`, e.g. `nb`) to that same window and draws `n_samples` per cell from the fitted law. `family`, `transform`, and `seed` are required, audited genome keys (ADR-021/ADR-022); `transform="log1p"` is illegal for count families and fails loud. In the closeness study (see `reports/closeness_experiment/`), `nb` is the family closest to conflictology on the C2ST indistinguishability metric.
96
+
97
+ ```python
98
+ ParametricConflictology(targets, window_months, partition_dict, loa, n_samples, family, transform="none", seed=42)
99
+ ```
100
+
101
+ #### ParametricHurdleConflictology — hurdle parametric climatology
102
+
103
+ A two-part law per unit: a **zero-spike** (empirical zero-rate, Bernoulli) plus a **continuous positive-part family** (`lognormal`/`gumbel`/`gamma`) fit to the positive window values (mirrors Vesco et al. 2026's RVI mixture). `transform` (`none`/`log1p`) applies to the positive part and is inverted **per sample**; a non-negativity floor (`EMIT_FLOOR`) guarantees no negative magnitudes even for `gumbel`. Closeness study: `gamma`/`none` is closest on magnitude fidelity (Wasserstein/energy); `log1p` is worse on active cells. (Tweedie was evaluated and excluded — ADR-022.)
104
+
105
+ ```python
106
+ ParametricHurdleConflictology(targets, window_months, partition_dict, loa, n_samples, family, transform="none", seed=42)
107
+ ```
108
+
109
+ ---
110
+
111
+ ## Model Catalog
112
+
113
+ The `BaselineModelCatalog` provides a factory for instantiating models based on config:
114
+
115
+ ```python
116
+ from views_baseline.model.catalog import BaselineModelCatalog
117
+
118
+ catalog = BaselineModelCatalog(config, partition_dict, loa)
119
+ model = catalog.get_model("LocfModel")
120
+ ```
121
+
122
+ Available models:
123
+
124
+ ```python
125
+ catalog.list_models()
126
+ # ['ZeroModel', 'LocfModel', 'AverageModel', 'ConflictologyModel', 'MixtureBaseline',
127
+ # 'ParametricConflictology', 'ParametricHurdleConflictology']
128
+ ```
129
+
130
+ ---
131
+
132
+ ## Integration with the VIEWS Pipeline
133
+
134
+ The package integrates with the core pipeline via:
135
+
136
+ ```python
137
+ BaselineForecastingModelManager
138
+ ```
139
+
140
+ Key characteristics:
141
+
142
+ * Models are fitted automatically via `fit()` and artifacts are pickled
143
+ * Predictions are generated per evaluation sequence
144
+ * Supports both evaluation and forecasting modes
145
+ * Distributional models are dispatched automatically via the `DistributionalBaselineModel` protocol
146
+
147
+ ---
148
+
149
+ ## Data Assumptions
150
+
151
+ Input DataFrame:
152
+
153
+ * Must be indexed by `(time, entity)` as a MultiIndex
154
+ * Must contain target columns specified in `config['targets']`
155
+
156
+ Example index:
157
+
158
+ ```text
159
+ MultiIndex(levels=[month_id, priogrid_id])
160
+ ```
161
+
162
+ ---
163
+
164
+ ## Notes & Caveats
165
+
166
+ * Entities without sufficient history are skipped (with a warning)
167
+ * No imputation beyond what the baseline logic implies
168
+ * No clipping or post-processing is applied by default
169
+
170
+ ---
171
+
172
+ ## Installation
173
+
174
+ Clone the repository and install with pip:
175
+
176
+ ```bash
177
+ pip install -e .
178
+ ```
179
+
180
+ ---
181
+
182
+ ## License / Usage
183
+
184
+ Internal VIEWS package. Intended for research and forecasting pipelines, not as a general-purpose forecasting library.
@@ -0,0 +1,63 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "views-baseline"
7
+ version = "1.0.0"
8
+ description = "VIEWS conflict-forecasting baseline models — point (Zero/LOCF/Average) and distributional (conflictology, mixture, parametric) baselines that emit views-frames PredictionFrames."
9
+ authors = [
10
+ { name = "Sonja Häffner", email = "sonhaf@prio.org" },
11
+ { name = "Simon Polichinel von der Maase", email = "simmaa@prio.org" },
12
+ ]
13
+ readme = "README.md"
14
+ requires-python = ">=3.11,<3.14"
15
+ license = "MIT"
16
+ license-files = ["LICENSE"]
17
+ # License is declared via the SPDX `license` expression above (PEP 639); the deprecated
18
+ # `License ::` classifier is intentionally omitted to avoid the expression/classifier clash.
19
+ classifiers = [
20
+ "Development Status :: 5 - Production/Stable",
21
+ "Intended Audience :: Science/Research",
22
+ "Operating System :: OS Independent",
23
+ "Programming Language :: Python :: 3",
24
+ "Programming Language :: Python :: 3.11",
25
+ "Programming Language :: Python :: 3.12",
26
+ "Programming Language :: Python :: 3.13",
27
+ "Topic :: Scientific/Engineering",
28
+ ]
29
+ dependencies = [
30
+ # views-frames is the canonical home of PredictionFrame / SpatioTemporalIndex /
31
+ # SpatialLevel, constructed at the single seam in model/frames/output.py (ADR-020).
32
+ "views-frames>=1.3.0,<2.0.0",
33
+ # Requires the views-frames re-export of PredictionFrame, which lands in
34
+ # views-pipeline-core 3.0.0 (#188 / PR #206). The suite asserts isinstance against
35
+ # that re-exported leaf, so 2.x (old local PredictionFrame) is not compatible.
36
+ "views-pipeline-core>=3.0.0,<4.0.0",
37
+ ]
38
+
39
+ [project.urls]
40
+ Homepage = "https://github.com/views-platform/views-baseline"
41
+ Repository = "https://github.com/views-platform/views-baseline"
42
+ Changelog = "https://github.com/views-platform/views-baseline/releases"
43
+
44
+ [dependency-groups]
45
+ dev = [
46
+ "pytest>=8.0",
47
+ "ruff>=0.8",
48
+ ]
49
+
50
+ [tool.hatch.build.targets.wheel]
51
+ packages = ["views_baseline"]
52
+
53
+ [tool.hatch.build.targets.sdist]
54
+ include = ["/views_baseline"]
55
+
56
+ [tool.pytest.ini_options]
57
+ filterwarnings = ["ignore::DeprecationWarning"]
58
+
59
+ [tool.ruff]
60
+ line-length = 99
61
+
62
+ [tool.ruff.lint]
63
+ select = ["E", "F", "W", "I"]
File without changes