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.
Files changed (69) hide show
  1. nextaire_tools-0.1.0/.gitignore +56 -0
  2. nextaire_tools-0.1.0/CHANGELOG.md +70 -0
  3. nextaire_tools-0.1.0/LICENSE +21 -0
  4. nextaire_tools-0.1.0/PKG-INFO +368 -0
  5. nextaire_tools-0.1.0/README.md +282 -0
  6. nextaire_tools-0.1.0/examples/README.md +26 -0
  7. nextaire_tools-0.1.0/notebooks/README.md +26 -0
  8. nextaire_tools-0.1.0/papers/README.md +42 -0
  9. nextaire_tools-0.1.0/pyproject.toml +197 -0
  10. nextaire_tools-0.1.0/reproductions/README.md +53 -0
  11. nextaire_tools-0.1.0/src/nextaire_tools/__init__.py +86 -0
  12. nextaire_tools-0.1.0/src/nextaire_tools/_typing.py +22 -0
  13. nextaire_tools-0.1.0/src/nextaire_tools/cli.py +127 -0
  14. nextaire_tools-0.1.0/src/nextaire_tools/exceptions.py +61 -0
  15. nextaire_tools-0.1.0/src/nextaire_tools/extractors/__init__.py +34 -0
  16. nextaire_tools-0.1.0/src/nextaire_tools/extractors/base.py +417 -0
  17. nextaire_tools-0.1.0/src/nextaire_tools/extractors/cams.py +136 -0
  18. nextaire_tools-0.1.0/src/nextaire_tools/extractors/era5.py +113 -0
  19. nextaire_tools-0.1.0/src/nextaire_tools/extractors/land.py +122 -0
  20. nextaire_tools-0.1.0/src/nextaire_tools/extractors/sampling.py +273 -0
  21. nextaire_tools-0.1.0/src/nextaire_tools/extractors/stations.py +189 -0
  22. nextaire_tools-0.1.0/src/nextaire_tools/io/__init__.py +7 -0
  23. nextaire_tools-0.1.0/src/nextaire_tools/io/readers.py +190 -0
  24. nextaire_tools-0.1.0/src/nextaire_tools/models/__init__.py +63 -0
  25. nextaire_tools-0.1.0/src/nextaire_tools/models/deep.py +611 -0
  26. nextaire_tools-0.1.0/src/nextaire_tools/models/evaluate.py +309 -0
  27. nextaire_tools-0.1.0/src/nextaire_tools/models/forecast.py +249 -0
  28. nextaire_tools-0.1.0/src/nextaire_tools/models/hybrid.py +333 -0
  29. nextaire_tools-0.1.0/src/nextaire_tools/models/interpret.py +163 -0
  30. nextaire_tools-0.1.0/src/nextaire_tools/models/sklearn_models.py +163 -0
  31. nextaire_tools-0.1.0/src/nextaire_tools/models/source_apportionment.py +197 -0
  32. nextaire_tools-0.1.0/src/nextaire_tools/models/splits.py +422 -0
  33. nextaire_tools-0.1.0/src/nextaire_tools/preprocessing/__init__.py +30 -0
  34. nextaire_tools-0.1.0/src/nextaire_tools/preprocessing/base.py +143 -0
  35. nextaire_tools-0.1.0/src/nextaire_tools/preprocessing/features.py +459 -0
  36. nextaire_tools-0.1.0/src/nextaire_tools/preprocessing/missing.py +307 -0
  37. nextaire_tools-0.1.0/src/nextaire_tools/preprocessing/outliers.py +351 -0
  38. nextaire_tools-0.1.0/src/nextaire_tools/preprocessing/pipeline.py +298 -0
  39. nextaire_tools-0.1.0/src/nextaire_tools/preprocessing/scaling.py +151 -0
  40. nextaire_tools-0.1.0/src/nextaire_tools/preprocessing/temporal.py +291 -0
  41. nextaire_tools-0.1.0/src/nextaire_tools/py.typed +0 -0
  42. nextaire_tools-0.1.0/src/nextaire_tools/utils/__init__.py +20 -0
  43. nextaire_tools-0.1.0/src/nextaire_tools/utils/logging.py +64 -0
  44. nextaire_tools-0.1.0/src/nextaire_tools/utils/validation.py +224 -0
  45. nextaire_tools-0.1.0/src/nextaire_tools/viz/__init__.py +47 -0
  46. nextaire_tools-0.1.0/src/nextaire_tools/viz/eda.py +457 -0
  47. nextaire_tools-0.1.0/src/nextaire_tools/viz/evaluation.py +294 -0
  48. nextaire_tools-0.1.0/src/nextaire_tools/viz/outliers.py +246 -0
  49. nextaire_tools-0.1.0/src/nextaire_tools/viz/style.py +276 -0
  50. nextaire_tools-0.1.0/tests/conftest.py +56 -0
  51. nextaire_tools-0.1.0/tests/test_cli.py +33 -0
  52. nextaire_tools-0.1.0/tests/test_extractors.py +82 -0
  53. nextaire_tools-0.1.0/tests/test_hybrid.py +93 -0
  54. nextaire_tools-0.1.0/tests/test_interpret.py +66 -0
  55. nextaire_tools-0.1.0/tests/test_io.py +49 -0
  56. nextaire_tools-0.1.0/tests/test_models_deep.py +65 -0
  57. nextaire_tools-0.1.0/tests/test_models_evaluate.py +74 -0
  58. nextaire_tools-0.1.0/tests/test_models_sklearn.py +36 -0
  59. nextaire_tools-0.1.0/tests/test_models_splits.py +68 -0
  60. nextaire_tools-0.1.0/tests/test_pipeline.py +55 -0
  61. nextaire_tools-0.1.0/tests/test_preprocessing_features.py +258 -0
  62. nextaire_tools-0.1.0/tests/test_preprocessing_missing.py +64 -0
  63. nextaire_tools-0.1.0/tests/test_preprocessing_outliers.py +75 -0
  64. nextaire_tools-0.1.0/tests/test_preprocessing_scaling.py +41 -0
  65. nextaire_tools-0.1.0/tests/test_preprocessing_temporal.py +77 -0
  66. nextaire_tools-0.1.0/tests/test_regressions.py +120 -0
  67. nextaire_tools-0.1.0/tests/test_reproduction_blocks.py +120 -0
  68. nextaire_tools-0.1.0/tests/test_source_apportionment.py +88 -0
  69. 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).