nextaire-tools 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.
- nextaire_tools-0.1.0/.gitignore +56 -0
- nextaire_tools-0.1.0/CHANGELOG.md +70 -0
- nextaire_tools-0.1.0/LICENSE +21 -0
- nextaire_tools-0.1.0/PKG-INFO +368 -0
- nextaire_tools-0.1.0/README.md +282 -0
- nextaire_tools-0.1.0/examples/README.md +26 -0
- nextaire_tools-0.1.0/notebooks/README.md +26 -0
- nextaire_tools-0.1.0/papers/README.md +42 -0
- nextaire_tools-0.1.0/pyproject.toml +197 -0
- nextaire_tools-0.1.0/reproductions/README.md +53 -0
- nextaire_tools-0.1.0/src/nextaire_tools/__init__.py +86 -0
- nextaire_tools-0.1.0/src/nextaire_tools/_typing.py +22 -0
- nextaire_tools-0.1.0/src/nextaire_tools/cli.py +127 -0
- nextaire_tools-0.1.0/src/nextaire_tools/exceptions.py +61 -0
- nextaire_tools-0.1.0/src/nextaire_tools/extractors/__init__.py +34 -0
- nextaire_tools-0.1.0/src/nextaire_tools/extractors/base.py +417 -0
- nextaire_tools-0.1.0/src/nextaire_tools/extractors/cams.py +136 -0
- nextaire_tools-0.1.0/src/nextaire_tools/extractors/era5.py +113 -0
- nextaire_tools-0.1.0/src/nextaire_tools/extractors/land.py +122 -0
- nextaire_tools-0.1.0/src/nextaire_tools/extractors/sampling.py +273 -0
- nextaire_tools-0.1.0/src/nextaire_tools/extractors/stations.py +189 -0
- nextaire_tools-0.1.0/src/nextaire_tools/io/__init__.py +7 -0
- nextaire_tools-0.1.0/src/nextaire_tools/io/readers.py +190 -0
- nextaire_tools-0.1.0/src/nextaire_tools/models/__init__.py +63 -0
- nextaire_tools-0.1.0/src/nextaire_tools/models/deep.py +611 -0
- nextaire_tools-0.1.0/src/nextaire_tools/models/evaluate.py +309 -0
- nextaire_tools-0.1.0/src/nextaire_tools/models/forecast.py +249 -0
- nextaire_tools-0.1.0/src/nextaire_tools/models/hybrid.py +333 -0
- nextaire_tools-0.1.0/src/nextaire_tools/models/interpret.py +163 -0
- nextaire_tools-0.1.0/src/nextaire_tools/models/sklearn_models.py +163 -0
- nextaire_tools-0.1.0/src/nextaire_tools/models/source_apportionment.py +197 -0
- nextaire_tools-0.1.0/src/nextaire_tools/models/splits.py +422 -0
- nextaire_tools-0.1.0/src/nextaire_tools/preprocessing/__init__.py +30 -0
- nextaire_tools-0.1.0/src/nextaire_tools/preprocessing/base.py +143 -0
- nextaire_tools-0.1.0/src/nextaire_tools/preprocessing/features.py +459 -0
- nextaire_tools-0.1.0/src/nextaire_tools/preprocessing/missing.py +307 -0
- nextaire_tools-0.1.0/src/nextaire_tools/preprocessing/outliers.py +351 -0
- nextaire_tools-0.1.0/src/nextaire_tools/preprocessing/pipeline.py +298 -0
- nextaire_tools-0.1.0/src/nextaire_tools/preprocessing/scaling.py +151 -0
- nextaire_tools-0.1.0/src/nextaire_tools/preprocessing/temporal.py +291 -0
- nextaire_tools-0.1.0/src/nextaire_tools/py.typed +0 -0
- nextaire_tools-0.1.0/src/nextaire_tools/utils/__init__.py +20 -0
- nextaire_tools-0.1.0/src/nextaire_tools/utils/logging.py +64 -0
- nextaire_tools-0.1.0/src/nextaire_tools/utils/validation.py +224 -0
- nextaire_tools-0.1.0/src/nextaire_tools/viz/__init__.py +47 -0
- nextaire_tools-0.1.0/src/nextaire_tools/viz/eda.py +457 -0
- nextaire_tools-0.1.0/src/nextaire_tools/viz/evaluation.py +294 -0
- nextaire_tools-0.1.0/src/nextaire_tools/viz/outliers.py +246 -0
- nextaire_tools-0.1.0/src/nextaire_tools/viz/style.py +276 -0
- nextaire_tools-0.1.0/tests/conftest.py +56 -0
- nextaire_tools-0.1.0/tests/test_cli.py +33 -0
- nextaire_tools-0.1.0/tests/test_extractors.py +82 -0
- nextaire_tools-0.1.0/tests/test_hybrid.py +93 -0
- nextaire_tools-0.1.0/tests/test_interpret.py +66 -0
- nextaire_tools-0.1.0/tests/test_io.py +49 -0
- nextaire_tools-0.1.0/tests/test_models_deep.py +65 -0
- nextaire_tools-0.1.0/tests/test_models_evaluate.py +74 -0
- nextaire_tools-0.1.0/tests/test_models_sklearn.py +36 -0
- nextaire_tools-0.1.0/tests/test_models_splits.py +68 -0
- nextaire_tools-0.1.0/tests/test_pipeline.py +55 -0
- nextaire_tools-0.1.0/tests/test_preprocessing_features.py +258 -0
- nextaire_tools-0.1.0/tests/test_preprocessing_missing.py +64 -0
- nextaire_tools-0.1.0/tests/test_preprocessing_outliers.py +75 -0
- nextaire_tools-0.1.0/tests/test_preprocessing_scaling.py +41 -0
- nextaire_tools-0.1.0/tests/test_preprocessing_temporal.py +77 -0
- nextaire_tools-0.1.0/tests/test_regressions.py +120 -0
- nextaire_tools-0.1.0/tests/test_reproduction_blocks.py +120 -0
- nextaire_tools-0.1.0/tests/test_source_apportionment.py +88 -0
- nextaire_tools-0.1.0/tests/test_viz.py +66 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Jupyter
|
|
2
|
+
.ipynb_checkpoints/
|
|
3
|
+
|
|
4
|
+
# Python cache
|
|
5
|
+
__pycache__/
|
|
6
|
+
*.py[cod]
|
|
7
|
+
*.pyo
|
|
8
|
+
|
|
9
|
+
# Virtual environment
|
|
10
|
+
.venv/
|
|
11
|
+
venv/
|
|
12
|
+
env/
|
|
13
|
+
|
|
14
|
+
# Packaging / build
|
|
15
|
+
build/
|
|
16
|
+
dist/
|
|
17
|
+
*.egg-info/
|
|
18
|
+
.eggs/
|
|
19
|
+
|
|
20
|
+
# Tooling caches
|
|
21
|
+
.mypy_cache/
|
|
22
|
+
.ruff_cache/
|
|
23
|
+
.pytest_cache/
|
|
24
|
+
.coverage
|
|
25
|
+
htmlcov/
|
|
26
|
+
coverage.xml
|
|
27
|
+
|
|
28
|
+
# Docs build
|
|
29
|
+
site/
|
|
30
|
+
|
|
31
|
+
# Downloaded reanalysis data (extractors write here)
|
|
32
|
+
data/era5/
|
|
33
|
+
data/cams/
|
|
34
|
+
data/era5_land/
|
|
35
|
+
*.grib
|
|
36
|
+
*.grb
|
|
37
|
+
*.grib2
|
|
38
|
+
*.nc
|
|
39
|
+
|
|
40
|
+
# Example outputs (generated by examples/end_to_end.py)
|
|
41
|
+
examples/figures/
|
|
42
|
+
examples/sample_station.csv
|
|
43
|
+
|
|
44
|
+
# Publisher PDFs kept locally for reference (not redistributed)
|
|
45
|
+
papers/*.pdf
|
|
46
|
+
|
|
47
|
+
# Reproduction outputs (generated by reproductions/*.py)
|
|
48
|
+
reproductions/outputs/
|
|
49
|
+
|
|
50
|
+
# Notebook run artifacts
|
|
51
|
+
notebooks/station.parquet
|
|
52
|
+
notebooks/*.png
|
|
53
|
+
|
|
54
|
+
# OS
|
|
55
|
+
.DS_Store
|
|
56
|
+
Thumbs.db
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to **nextaire_tools** are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **Paper reproductions** — runnable, offline recipes in `reproductions/` that
|
|
13
|
+
rebuild the methodology of the three studies `nextaire_tools` is based on (Petrić et al.
|
|
14
|
+
2024; Jiménez-Navarro et al. 2024; Račić et al. 2026). See `papers/README.md`.
|
|
15
|
+
- **Example notebooks** — `notebooks/` with a quickstart, a preprocessing/feature
|
|
16
|
+
tour, interactive paper reproductions, and a deep-learning + Prophet forecasting
|
|
17
|
+
tour; each runs on synthetic data and is committed with executed outputs. Install
|
|
18
|
+
with `pip install "nextaire_tools[notebooks]"`.
|
|
19
|
+
- **Preprocessing**:
|
|
20
|
+
- `OutlierHandler` gains a `rolling_sigma` method — time-local winsorisation on
|
|
21
|
+
a centred rolling window (`window`, `sigma`), for spike removal.
|
|
22
|
+
- `MissingValueHandler` gains an `iterative` strategy — multivariate
|
|
23
|
+
(round-robin) imputation via scikit-learn's `IterativeImputer`, with a
|
|
24
|
+
configurable `estimator` (e.g. Bayesian ridge).
|
|
25
|
+
- `WindDecomposer` — wind direction → sine/cosine (x/y) and speed from u/v
|
|
26
|
+
components.
|
|
27
|
+
- `LagFeatures` — causal lag and rolling-aggregate (e.g. 12-hour median)
|
|
28
|
+
features.
|
|
29
|
+
- `CorrelationFilter` — drop features above a pairwise-correlation threshold.
|
|
30
|
+
- **Models**:
|
|
31
|
+
- `HybridProphetRegressor` / `ProphetFeatures` — Prophet forecasts as features
|
|
32
|
+
for a downstream regressor (the "hybrid" model).
|
|
33
|
+
- `NMFApportionment` — rank-k NMF factor analysis for source apportionment.
|
|
34
|
+
- `permutation_importance_report`, `tree_shap_values`, `shap_importance` —
|
|
35
|
+
model-interpretation helpers.
|
|
36
|
+
- `make_regressor` registers `decision_tree` and (optional) `xgboost`.
|
|
37
|
+
- **Metrics** — `regression_metrics` adds `wape`, `nmae`, and `nrmse`
|
|
38
|
+
(IQR-normalised).
|
|
39
|
+
- **Optional extras** — `nextaire_tools[boost]` (XGBoost) and `nextaire_tools[shap]` (TreeSHAP).
|
|
40
|
+
|
|
41
|
+
## [0.1.0] — 2026-07-05
|
|
42
|
+
|
|
43
|
+
Initial public release.
|
|
44
|
+
|
|
45
|
+
### Added
|
|
46
|
+
|
|
47
|
+
- **IO** — `load_table` / `save_table` for CSV, Excel, and Parquet with optional
|
|
48
|
+
datetime-index handling.
|
|
49
|
+
- **Preprocessing** (scikit-learn–compatible, DataFrame-in/DataFrame-out steps):
|
|
50
|
+
- `MissingValueHandler` — drop / impute / interpolate missing values, missingness
|
|
51
|
+
indicators, and column dropping by missing fraction.
|
|
52
|
+
- `OutlierHandler` — IQR, z-score, modified z-score, quantile, and Isolation-Forest
|
|
53
|
+
detection with clip / drop / NaN / flag strategies.
|
|
54
|
+
- `TemporalFeatures` — calendar features plus cyclical (sine/cosine) encodings for
|
|
55
|
+
hour, day-of-week, month, and day-of-year.
|
|
56
|
+
- `Scaler` — standard / min-max / robust / max-abs scaling with `inverse_transform`.
|
|
57
|
+
- `Pipeline` / `make_pipeline` — compose steps; build from config.
|
|
58
|
+
- **Extractors** for the Copernicus data stores:
|
|
59
|
+
- `ERA5Extractor` (Climate Data Store), `CAMSExtractor` (Atmosphere Data Store),
|
|
60
|
+
`ERA5LandExtractor` (ERA5-Land) with nearest-neighbour point sampling at stations.
|
|
61
|
+
- `load_stations`, `dms_to_dd`, and GRIB sampling helpers.
|
|
62
|
+
- **Visualization** — a colorblind-safe, light/dark-aware plotting theme with EDA,
|
|
63
|
+
outlier, and model-evaluation figures.
|
|
64
|
+
- **Models** — time-series cross-validation splitters, air-quality regression metrics
|
|
65
|
+
(incl. index of agreement and FAC2), a scikit-learn regressor factory, PyTorch
|
|
66
|
+
deep-learning regressors (MLP / LSTM / CNN), and a Prophet forecasting wrapper.
|
|
67
|
+
- Full MkDocs documentation site, typed API (`py.typed`), and a test suite.
|
|
68
|
+
|
|
69
|
+
[Unreleased]: https://github.com/NextAIRE-Horizon/nextaire_tools/compare/v0.1.0...HEAD
|
|
70
|
+
[0.1.0]: https://github.com/NextAIRE-Horizon/nextaire_tools/releases/tag/v0.1.0
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Valentino Petric
|
|
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,368 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: nextaire_tools
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Reproducible preprocessing, feature engineering, Copernicus data extraction, visualization and ML/DL modeling for air-quality time series.
|
|
5
|
+
Project-URL: Homepage, https://github.com/NextAIRE-Horizon/nextaire_tools
|
|
6
|
+
Project-URL: Documentation, https://nextaire-tools.readthedocs.io
|
|
7
|
+
Project-URL: Repository, https://github.com/NextAIRE-Horizon/nextaire_tools
|
|
8
|
+
Project-URL: Issues, https://github.com/NextAIRE-Horizon/nextaire_tools/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/NextAIRE-Horizon/nextaire_tools/blob/main/CHANGELOG.md
|
|
10
|
+
Author-email: Valentino Petrić <valentino.petric@lisboncouncil.net>
|
|
11
|
+
Maintainer-email: Valentino Petrić <valentino.petric@lisboncouncil.net>
|
|
12
|
+
License: MIT
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Keywords: CAMS,Copernicus,ERA5,air quality,atmospheric science,deep learning,feature engineering,machine learning,preprocessing,reanalysis,time series
|
|
15
|
+
Classifier: Development Status :: 4 - Beta
|
|
16
|
+
Classifier: Intended Audience :: Science/Research
|
|
17
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
18
|
+
Classifier: Operating System :: OS Independent
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
24
|
+
Classifier: Topic :: Scientific/Engineering :: Atmospheric Science
|
|
25
|
+
Classifier: Typing :: Typed
|
|
26
|
+
Requires-Python: >=3.10
|
|
27
|
+
Requires-Dist: matplotlib>=3.7
|
|
28
|
+
Requires-Dist: numpy>=1.24
|
|
29
|
+
Requires-Dist: openpyxl>=3.1
|
|
30
|
+
Requires-Dist: pandas>=2.1
|
|
31
|
+
Requires-Dist: pyarrow>=14
|
|
32
|
+
Requires-Dist: scikit-learn>=1.4
|
|
33
|
+
Requires-Dist: scipy>=1.10
|
|
34
|
+
Requires-Dist: seaborn>=0.13
|
|
35
|
+
Provides-Extra: all
|
|
36
|
+
Requires-Dist: cdsapi>=0.7.2; extra == 'all'
|
|
37
|
+
Requires-Dist: cfgrib>=0.9.11; extra == 'all'
|
|
38
|
+
Requires-Dist: eccodes>=2.30; extra == 'all'
|
|
39
|
+
Requires-Dist: geopandas>=1.0; extra == 'all'
|
|
40
|
+
Requires-Dist: holidays>=0.50; extra == 'all'
|
|
41
|
+
Requires-Dist: osmnx>=2.0; extra == 'all'
|
|
42
|
+
Requires-Dist: prophet>=1.1; extra == 'all'
|
|
43
|
+
Requires-Dist: shap>=0.44; extra == 'all'
|
|
44
|
+
Requires-Dist: shapely>=2.0; extra == 'all'
|
|
45
|
+
Requires-Dist: torch>=2.0; extra == 'all'
|
|
46
|
+
Requires-Dist: xarray>=2024.1; extra == 'all'
|
|
47
|
+
Requires-Dist: xgboost>=2.0; extra == 'all'
|
|
48
|
+
Provides-Extra: boost
|
|
49
|
+
Requires-Dist: xgboost>=2.0; extra == 'boost'
|
|
50
|
+
Provides-Extra: deep
|
|
51
|
+
Requires-Dist: torch>=2.0; extra == 'deep'
|
|
52
|
+
Provides-Extra: dev
|
|
53
|
+
Requires-Dist: build>=1.2; extra == 'dev'
|
|
54
|
+
Requires-Dist: mypy>=1.11; extra == 'dev'
|
|
55
|
+
Requires-Dist: pandas-stubs; extra == 'dev'
|
|
56
|
+
Requires-Dist: pytest-cov>=5; extra == 'dev'
|
|
57
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
58
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
59
|
+
Provides-Extra: docs
|
|
60
|
+
Requires-Dist: griffe>=1.0; extra == 'docs'
|
|
61
|
+
Requires-Dist: mkdocs-gen-files>=0.5; extra == 'docs'
|
|
62
|
+
Requires-Dist: mkdocs-literate-nav>=0.6; extra == 'docs'
|
|
63
|
+
Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
|
|
64
|
+
Requires-Dist: mkdocs>=1.6; extra == 'docs'
|
|
65
|
+
Requires-Dist: mkdocstrings[python]>=0.26; extra == 'docs'
|
|
66
|
+
Provides-Extra: extract
|
|
67
|
+
Requires-Dist: cdsapi>=0.7.2; extra == 'extract'
|
|
68
|
+
Requires-Dist: cfgrib>=0.9.11; extra == 'extract'
|
|
69
|
+
Requires-Dist: eccodes>=2.30; extra == 'extract'
|
|
70
|
+
Requires-Dist: xarray>=2024.1; extra == 'extract'
|
|
71
|
+
Provides-Extra: forecast
|
|
72
|
+
Requires-Dist: prophet>=1.1; extra == 'forecast'
|
|
73
|
+
Provides-Extra: geo
|
|
74
|
+
Requires-Dist: geopandas>=1.0; extra == 'geo'
|
|
75
|
+
Requires-Dist: osmnx>=2.0; extra == 'geo'
|
|
76
|
+
Requires-Dist: shapely>=2.0; extra == 'geo'
|
|
77
|
+
Provides-Extra: holidays
|
|
78
|
+
Requires-Dist: holidays>=0.50; extra == 'holidays'
|
|
79
|
+
Provides-Extra: notebooks
|
|
80
|
+
Requires-Dist: ipykernel>=6; extra == 'notebooks'
|
|
81
|
+
Requires-Dist: jupyterlab>=4; extra == 'notebooks'
|
|
82
|
+
Requires-Dist: nbconvert>=7; extra == 'notebooks'
|
|
83
|
+
Provides-Extra: shap
|
|
84
|
+
Requires-Dist: shap>=0.44; extra == 'shap'
|
|
85
|
+
Description-Content-Type: text/markdown
|
|
86
|
+
|
|
87
|
+
# nextaire_tools
|
|
88
|
+
|
|
89
|
+
Preprocessing, feature engineering, Copernicus data extraction, visualization,
|
|
90
|
+
and ML/DL modeling for air-quality time series.
|
|
91
|
+
|
|
92
|
+
`nextaire_tools` collects the steps a typical air-quality study repeats by hand — loading
|
|
93
|
+
a table, handling missing values and outliers, building calendar features,
|
|
94
|
+
pulling ERA5/CAMS reanalysis, plotting, and fitting a model with a correct
|
|
95
|
+
time-series split — into a set of composable, tested, scikit-learn-compatible
|
|
96
|
+
building blocks. It implements the methods from three peer-reviewed studies (see
|
|
97
|
+
[Reproducing the papers](#reproducing-the-papers)), so a study can be rebuilt from
|
|
98
|
+
documented, versioned code rather than one-off notebooks.
|
|
99
|
+
|
|
100
|
+
## Contents
|
|
101
|
+
|
|
102
|
+
- [Installation](#installation)
|
|
103
|
+
- [Main features](#main-features)
|
|
104
|
+
- [Example data](#example-data)
|
|
105
|
+
- [Quickstart](#quickstart)
|
|
106
|
+
- [Tutorials & notebooks](#tutorials--notebooks)
|
|
107
|
+
- [Reproducing the papers](#reproducing-the-papers)
|
|
108
|
+
- [Documentation](#documentation)
|
|
109
|
+
- [Citing nextaire_tools](#citing-nextaire_tools)
|
|
110
|
+
|
|
111
|
+
## Installation
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
pip install nextaire_tools # core: IO, preprocessing, viz, sklearn models
|
|
115
|
+
pip install "nextaire_tools[deep]" # + PyTorch (MLP / LSTM / CNN)
|
|
116
|
+
pip install "nextaire_tools[extract]" # + Copernicus (cdsapi, xarray, cfgrib)
|
|
117
|
+
pip install "nextaire_tools[forecast]" # + Prophet (and the Prophet→RF hybrid)
|
|
118
|
+
pip install "nextaire_tools[boost]" # + XGBoost
|
|
119
|
+
pip install "nextaire_tools[shap]" # + TreeSHAP interpretation
|
|
120
|
+
pip install "nextaire_tools[geo]" # + geospatial land-use features (geopandas, osmnx)
|
|
121
|
+
pip install "nextaire_tools[notebooks]" # + JupyterLab to run the example notebooks
|
|
122
|
+
pip install "nextaire_tools[all]" # everything
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Main features
|
|
126
|
+
|
|
127
|
+
Everything is a `DataFrame`-in / `DataFrame`-out step, so column names and the
|
|
128
|
+
`DatetimeIndex` survive from raw table to fitted model.
|
|
129
|
+
|
|
130
|
+
### Data IO
|
|
131
|
+
|
|
132
|
+
`load_table` reads CSV, Excel, and Parquet into a datetime-indexed `DataFrame`
|
|
133
|
+
(with DMS/decimal coordinate handling for station files); `save_table` writes them
|
|
134
|
+
back. One entry point for messy inputs.
|
|
135
|
+
|
|
136
|
+
### Cleaning
|
|
137
|
+
|
|
138
|
+
Scikit-learn transformers for the parts every study gets slightly differently:
|
|
139
|
+
|
|
140
|
+
- `MissingValueHandler` — drop, statistical / directional fill, time-aware
|
|
141
|
+
interpolation, or multivariate **iterative** imputation.
|
|
142
|
+
- `OutlierHandler` — IQR, z-score, modified z-score, quantile bounds, a
|
|
143
|
+
multivariate Isolation Forest, or **`rolling_sigma`** time-local winsorisation
|
|
144
|
+
that removes short spikes without deleting real pollution events.
|
|
145
|
+
|
|
146
|
+
### Feature engineering
|
|
147
|
+
|
|
148
|
+
- `TemporalFeatures` — calendar fields plus sine/cosine encodings (hour 23 is
|
|
149
|
+
adjacent to hour 0).
|
|
150
|
+
- `WindDecomposer` — wind direction → x/y components, and speed from u/v.
|
|
151
|
+
- `LagFeatures` — causal lags and rolling aggregates (e.g. 12-hour median).
|
|
152
|
+
- `CorrelationFilter` — drop collinear features above a threshold.
|
|
153
|
+
- `Scaler` and `Pipeline` / `make_pipeline` compose it all reproducibly.
|
|
154
|
+
|
|
155
|
+
### Copernicus extraction
|
|
156
|
+
|
|
157
|
+
`ERA5Extractor`, `CAMSExtractor`, and `ERA5LandExtractor` download from the
|
|
158
|
+
Climate/Atmosphere Data Stores and sample the grid at your monitoring stations;
|
|
159
|
+
`load_stations` parses the coordinate files.
|
|
160
|
+
|
|
161
|
+
### Visualization
|
|
162
|
+
|
|
163
|
+
A colorblind-safe, light/dark-aware Matplotlib theme with EDA
|
|
164
|
+
(`plot_missingness`, `plot_correlation`, `plot_seasonality`, `plot_timeseries`),
|
|
165
|
+
outlier inspection, and evaluation (`plot_predictions`, `plot_residuals`,
|
|
166
|
+
`plot_feature_importance`) figures.
|
|
167
|
+
|
|
168
|
+
### Modeling & validation
|
|
169
|
+
|
|
170
|
+
- Leakage-free splitters: `BlockingTimeSeriesSplit`, `SlidingWindowSplit`,
|
|
171
|
+
`ExpandingWindowSplit`, `temporal_train_test_split`.
|
|
172
|
+
- `make_regressor` — Random Forest, gradient boosting, decision tree, linear
|
|
173
|
+
family, KNN, SVR, and optional XGBoost by name.
|
|
174
|
+
- PyTorch `MLPRegressor` / `LSTMRegressor` / `CNNRegressor`, a `ProphetForecaster`,
|
|
175
|
+
and a `HybridProphetRegressor` (Prophet forecasts as features for a regressor).
|
|
176
|
+
- `NMFApportionment` for NMF source apportionment.
|
|
177
|
+
|
|
178
|
+
### Metrics & interpretation
|
|
179
|
+
|
|
180
|
+
`regression_metrics` reports the scores atmospheric scientists actually use —
|
|
181
|
+
MAE/RMSE/R², index of agreement, FAC2, and IQR-normalized nMAE/nRMSE plus WAPE —
|
|
182
|
+
and `cross_val_report` tabulates them per fold. `permutation_importance_report`
|
|
183
|
+
and `shap_importance` (TreeSHAP) explain a fitted model.
|
|
184
|
+
|
|
185
|
+
### Module map
|
|
186
|
+
|
|
187
|
+
| Subpackage | Key names |
|
|
188
|
+
|------------|-----------|
|
|
189
|
+
| `nextaire_tools.io` | `load_table`, `save_table` |
|
|
190
|
+
| `nextaire_tools.preprocessing` | `MissingValueHandler`, `OutlierHandler`, `TemporalFeatures`, `WindDecomposer`, `LagFeatures`, `CorrelationFilter`, `Scaler`, `Pipeline` |
|
|
191
|
+
| `nextaire_tools.extractors` | `ERA5Extractor`, `CAMSExtractor`, `ERA5LandExtractor`, `load_stations` |
|
|
192
|
+
| `nextaire_tools.viz` | `plot_missingness`, `plot_seasonality`, `plot_predictions`, … |
|
|
193
|
+
| `nextaire_tools.models` | `make_regressor`, `BlockingTimeSeriesSplit`, `cross_val_report`, `regression_metrics`, `LSTMRegressor`, `HybridProphetRegressor`, `NMFApportionment`, `shap_importance` |
|
|
194
|
+
|
|
195
|
+
## Example data
|
|
196
|
+
|
|
197
|
+
No downloads are needed to try `nextaire_tools`. The generators in
|
|
198
|
+
[`reproductions/_synthetic.py`](reproductions/_synthetic.py) build data with the
|
|
199
|
+
same columns, frequency, and structure as the papers' real datasets:
|
|
200
|
+
|
|
201
|
+
- `make_graz_hourly()` / `make_graz_multistation()` — hourly pollutants
|
|
202
|
+
(NO/NO₂/O₃/PM10) plus ground meteorology and ERA5-style reanalysis, with
|
|
203
|
+
realistic diurnal/seasonal cycles, gaps, and spikes.
|
|
204
|
+
- `make_zagreb_daily()` — daily PM10-bound PAHs and metals with meteorology,
|
|
205
|
+
traffic and heating proxies, and station labels.
|
|
206
|
+
|
|
207
|
+
```python
|
|
208
|
+
import sys; sys.path.insert(0, "reproductions")
|
|
209
|
+
from _synthetic import make_graz_hourly
|
|
210
|
+
|
|
211
|
+
df = make_graz_hourly(n_days=180, seed=0) # hourly, datetime-indexed DataFrame
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
For real data, replace the generator with `nextaire_tools.load_table("station.csv",
|
|
215
|
+
time_col="timestamp", set_time_index=True)`.
|
|
216
|
+
|
|
217
|
+
## Quickstart
|
|
218
|
+
|
|
219
|
+
```python
|
|
220
|
+
import nextaire_tools
|
|
221
|
+
from nextaire_tools import load_table, Pipeline
|
|
222
|
+
from nextaire_tools.preprocessing import MissingValueHandler, OutlierHandler, TemporalFeatures, Scaler
|
|
223
|
+
|
|
224
|
+
# 1. Load anything (CSV / Excel / Parquet) with a datetime index
|
|
225
|
+
df = load_table("station.csv", time_col="timestamp", set_time_index=True)
|
|
226
|
+
|
|
227
|
+
# 2. Build a reproducible cleaning + feature pipeline
|
|
228
|
+
pipe = Pipeline([
|
|
229
|
+
MissingValueHandler(strategy="interpolate", limit=3),
|
|
230
|
+
OutlierHandler(columns=["no2", "o3", "pm10"], method="iqr", strategy="clip"),
|
|
231
|
+
TemporalFeatures(
|
|
232
|
+
add=("hour", "dayofweek", "month", "is_weekend"),
|
|
233
|
+
cyclical=("hour", "dayofweek", "dayofyear"), # sin/cos encodings
|
|
234
|
+
),
|
|
235
|
+
Scaler(method="standard"),
|
|
236
|
+
])
|
|
237
|
+
clean = pipe.fit_transform(df)
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### Explore before you model
|
|
241
|
+
|
|
242
|
+
```python
|
|
243
|
+
from nextaire_tools.viz import plot_missingness, plot_correlation, plot_seasonality
|
|
244
|
+
|
|
245
|
+
plot_missingness(df)
|
|
246
|
+
plot_correlation(df, cluster=True)
|
|
247
|
+
plot_seasonality(df, column="o3", by="hour")
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### Fit a model with a correct time-series split
|
|
251
|
+
|
|
252
|
+
```python
|
|
253
|
+
from nextaire_tools.models import make_regressor, cross_val_report, BlockingTimeSeriesSplit
|
|
254
|
+
|
|
255
|
+
target = "no2"
|
|
256
|
+
X = clean.drop(columns=[target])
|
|
257
|
+
y = clean[target]
|
|
258
|
+
|
|
259
|
+
model = make_regressor("random_forest", n_estimators=400)
|
|
260
|
+
report = cross_val_report(model, X, y, cv=BlockingTimeSeriesSplit(n_splits=5))
|
|
261
|
+
print(report) # per-fold MAE / RMSE / R² / nMAE / nRMSE / IoA / FAC2 + mean & std
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### Deep learning (optional `[deep]`)
|
|
265
|
+
|
|
266
|
+
```python
|
|
267
|
+
from nextaire_tools.models import LSTMRegressor
|
|
268
|
+
|
|
269
|
+
lstm = LSTMRegressor(window=24, hidden_size=64, epochs=50)
|
|
270
|
+
lstm.fit(X.values, y.values)
|
|
271
|
+
y_hat = lstm.predict(X.values)
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Pull ERA5 meteorology at your stations (optional `[extract]`)
|
|
275
|
+
|
|
276
|
+
```python
|
|
277
|
+
from nextaire_tools.extractors import ERA5Extractor, load_stations
|
|
278
|
+
|
|
279
|
+
stations = load_stations("data/Coordinates.xlsx") # handles DMS or decimal degrees
|
|
280
|
+
era5 = ERA5Extractor(output_dir="data/era5")
|
|
281
|
+
frames = era5.extract_to_frames(
|
|
282
|
+
stations=stations,
|
|
283
|
+
variables=["2m_temperature", "10m_u_component_of_wind", "boundary_layer_height"],
|
|
284
|
+
area=[49.1, 9.53, 46.3, 17.16], # N, W, S, E
|
|
285
|
+
start="2024-01-01", end="2024-01-31",
|
|
286
|
+
save_dir="data/era5",
|
|
287
|
+
)
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
## Tutorials & notebooks
|
|
291
|
+
|
|
292
|
+
Runnable Jupyter notebooks in [`notebooks/`](notebooks/) work on the synthetic
|
|
293
|
+
example data — no network required — and are committed with their executed
|
|
294
|
+
outputs, so they render on GitHub and re-run top to bottom.
|
|
295
|
+
|
|
296
|
+
| Notebook | What it covers |
|
|
297
|
+
|----------|----------------|
|
|
298
|
+
| [`01_quickstart.ipynb`](notebooks/01_quickstart.ipynb) | Load/save, EDA plots, a cleaning + feature `Pipeline`, and a Random Forest with a leakage-free split. |
|
|
299
|
+
| [`02_preprocessing_and_features.ipynb`](notebooks/02_preprocessing_and_features.ipynb) | Every preprocessing step in turn, then composed into one pipeline. |
|
|
300
|
+
| [`03_reproduce_papers.ipynb`](notebooks/03_reproduce_papers.ipynb) | Compact interactive versions of the three paper recipes. |
|
|
301
|
+
| [`04_deep_learning_and_forecasting.ipynb`](notebooks/04_deep_learning_and_forecasting.ipynb) | PyTorch MLP/LSTM/CNN regressors and Prophet / hybrid Prophet+RF forecasting. |
|
|
302
|
+
|
|
303
|
+
```bash
|
|
304
|
+
pip install "nextaire_tools[notebooks]"
|
|
305
|
+
jupyter lab # open a notebook, or run headless:
|
|
306
|
+
jupyter nbconvert --to notebook --execute --inplace notebooks/01_quickstart.ipynb
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
There is also a plain-script walkthrough in
|
|
310
|
+
[`examples/end_to_end.py`](examples/end_to_end.py) and a prose tutorial in the
|
|
311
|
+
[documentation](https://nextaire-tools.readthedocs.io).
|
|
312
|
+
|
|
313
|
+
## Reproducing the papers
|
|
314
|
+
|
|
315
|
+
`nextaire_tools` packages the methodology of three peer-reviewed air-quality ML studies.
|
|
316
|
+
Each has a runnable recipe in [`reproductions/`](reproductions/) that rebuilds its
|
|
317
|
+
data construction, preprocessing, cross-validation, models, and metrics — offline,
|
|
318
|
+
on synthetic data of the same shape, degrading gracefully when an optional
|
|
319
|
+
dependency is missing. Swap the synthetic generator for `load_table(...)` of the
|
|
320
|
+
real data to reproduce the published numbers.
|
|
321
|
+
|
|
322
|
+
```bash
|
|
323
|
+
pip install "nextaire_tools[all]"
|
|
324
|
+
python reproductions/paper1_petric2024_aaqr.py # Petrić et al. 2024 (AAQR)
|
|
325
|
+
python reproductions/paper2_jimenez2024_multitarget.py # Jiménez-Navarro et al. 2024 (Results in Eng.)
|
|
326
|
+
python reproductions/paper3_racic2026_source_apportionment.py # Račić et al. 2026 (Atmos. Env. X)
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
See [`reproductions/README.md`](reproductions/README.md) for the full paper →
|
|
330
|
+
API map, and [`papers/README.md`](papers/README.md) for citations and data
|
|
331
|
+
sources.
|
|
332
|
+
|
|
333
|
+
## Documentation
|
|
334
|
+
|
|
335
|
+
Full documentation — user guide, API reference, and tutorials — lives at
|
|
336
|
+
**[nextaire-tools.readthedocs.io](https://nextaire-tools.readthedocs.io)** and is built from the
|
|
337
|
+
Markdown sources in [`docs/`](docs/).
|
|
338
|
+
|
|
339
|
+
Build it locally:
|
|
340
|
+
|
|
341
|
+
```bash
|
|
342
|
+
pip install "nextaire_tools[docs]"
|
|
343
|
+
mkdocs serve # http://127.0.0.1:8000
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
## Citing nextaire_tools
|
|
347
|
+
|
|
348
|
+
If you use `nextaire_tools` in academic work, please cite the relevant methodological
|
|
349
|
+
paper(s) it is based on:
|
|
350
|
+
|
|
351
|
+
> Petrić, V., Hussain, H., Časni, K., et al. (2024). *Ensemble Machine Learning,
|
|
352
|
+
> Deep Learning, and Time Series Forecasting: Improving Prediction Accuracy for
|
|
353
|
+
> Hourly Concentrations of Ambient Air Pollutants.* Aerosol and Air Quality Research,
|
|
354
|
+
> 24, 230317. doi:10.4209/aaqr.230317
|
|
355
|
+
|
|
356
|
+
> Jiménez-Navarro, M. J., Lovrić, M., Kecorius, S., Nyarko, E. K.,
|
|
357
|
+
> Martínez-Ballesteros, M. (2024). *Explainable deep learning on multi-target time
|
|
358
|
+
> series forecasting: An air pollution use case.* Results in Engineering, 24,
|
|
359
|
+
> 103290. doi:10.1016/j.rineng.2024.103290
|
|
360
|
+
|
|
361
|
+
> Račić, N., Ružičić, S., Petrić, V., et al. (2026). *Assessment of contributors to
|
|
362
|
+
> airborne PAHs and heavy metals in PM₁₀ using temporal, spatial, traffic and
|
|
363
|
+
> heating data in explainable machine learning models.* Atmospheric Environment: X,
|
|
364
|
+
> 29, 100413. doi:10.1016/j.aeaoa.2026.100413
|
|
365
|
+
|
|
366
|
+
## License
|
|
367
|
+
|
|
368
|
+
`nextaire_tools` is released under the [MIT License](LICENSE).
|