Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,20 @@ jobs:
- name: Build wheel and source distribution
run: python -m build

- name: Verify wheel package layout
run: |
python - <<'PY'
import glob
import zipfile

wheel, = glob.glob("dist/*.whl")
with zipfile.ZipFile(wheel) as archive:
names = set(archive.namelist())

assert "aep_load_forecasting/__init__.py" in names
assert "src/__init__.py" not in names
PY

- name: Smoke-test installed console commands
run: |
aep-demo --help
Expand Down
51 changes: 27 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,16 @@ out-of-time test set:
## Approach

- **Target:** hourly load `AEP_MW`.
- **Features** (`src/make_features.py`):
- **Features** (`src/aep_load_forecasting/make_features.py`):
- Calendar: `hour`, `dayofweek`, `month`, `is_weekend`
- Lags: `lag_1` (1 h), `lag_24` (1 day), `lag_168` (1 week)
- Rolling means (shifted, past-only): `roll_24_mean`, `roll_168_mean`
- **Split** (`src/xgb_eval.py`): disjoint, time-based partitions — train
- **Split** (`src/aep_load_forecasting/xgb_eval.py`): disjoint, time-based
partitions — train
(everything before the final 60 days), validation (720 h), and test (720 h).
- **Model:** `XGBRegressor(n_estimators=800, learning_rate=0.05, max_depth=6, subsample=0.8, colsample_bytree=0.8)`.
- **24-hour forecast** (`src/forecasting.py`, `src/forecast_24h.py`): the
- **24-hour forecast** (`src/aep_load_forecasting/forecasting.py`,
`src/aep_load_forecasting/forecast_24h.py`): the
final model is fit on all data, then steps hour-by-hour, feeding each
prediction back in as the next `lag_1`. The reusable forecast core validates
the hourly history and model feature contract before prediction.
Expand All @@ -65,16 +67,17 @@ out-of-time test set:
```text
load-forecasting-xgboost/
├── src/
│ ├── plot_load.py # EDA: plot the last 14 days of load
│ ├── make_features.py # build calendar + lag + rolling features
│ ├── sample_data.py # deterministic dataset for a no-download demo
│ ├── evaluation.py # leakage-safe chronological split utilities
│ ├── reporting.py # reusable metrics and CSV report persistence
│ ├── forecasting.py # validated recursive forecast utilities
│ ├── demo_data.py # forecast CSV validation for the demo
│ ├── baseline_eval.py # naive baselines (yesterday / last week / blend)
│ ├── xgb_eval.py # train + evaluate XGBoost vs. baseline (last 30 days)
│ └── forecast_24h.py # final model + recursive next-24h forecast export
│ └── aep_load_forecasting/
│ ├── plot_load.py # EDA: plot the last 14 days of load
│ ├── make_features.py # build calendar + lag + rolling features
│ ├── sample_data.py # deterministic no-download dataset
│ ├── evaluation.py # chronological split utilities
│ ├── reporting.py # metrics and CSV report persistence
│ ├── forecasting.py # validated recursive forecast utilities
│ ├── demo_data.py # forecast CSV validation for the demo
│ ├── baseline_eval.py # yesterday / last-week / blend baselines
│ ├── xgb_eval.py # train and evaluate XGBoost
│ └── forecast_24h.py # export a recursive future forecast
├── streamlit_app.py # interactive demo (reads assets/forecast_next24h.csv)
├── assets/ # plots + a sample forecast CSV
├── requirements.txt
Expand Down Expand Up @@ -102,8 +105,8 @@ This single command creates the sample data, features, baseline and XGBoost
metrics, evaluation plots, trained model, and next-24h forecast under the
existing ignored `data/`, `reports/`, and `models/` directories. Use
`--output-dir` to keep every artifact below a different directory. The module
form, `python -m src.demo_pipeline`, remains available when working directly
from a source checkout.
form, `python -m aep_load_forecasting.demo_pipeline`, remains available after
installing the project.

Each successful run also writes `reports/sample_run_manifest.json`. The
manifest records the effective pipeline parameters, Python and dependency
Expand Down Expand Up @@ -160,30 +163,30 @@ mkdir data
# -> place AEP_hourly.csv in .\data\

# 2) build the validated feature table
python -m src.make_features
aep-make-features

# 3) evaluate baselines and XGBoost (test = last 30 days)
python -m src.baseline_eval
python -m src.xgb_eval
aep-baseline-eval
aep-xgb-eval

# 4) train the final model and export the next-24h forecast
python -m src.forecast_24h
aep-forecast
```

The evaluation commands create reproducible artifacts automatically:

| Command | Metrics | Plot |
|---|---|---|
| `python -m src.baseline_eval` | `reports/baseline_metrics.csv` | `reports/figures/baseline_evaluation.png` |
| `python -m src.xgb_eval` | `reports/xgb_evaluation_metrics.csv` | `reports/figures/xgb_evaluation.png` |
| `python -m src.forecast_24h` | `reports/forecast_next24h.csv` | `reports/figures/forecast_next24h.png` |
| `aep-baseline-eval` | `reports/baseline_metrics.csv` | `reports/figures/baseline_evaluation.png` |
| `aep-xgb-eval` | `reports/xgb_evaluation_metrics.csv` | `reports/figures/xgb_evaluation.png` |
| `aep-forecast` | `reports/forecast_next24h.csv` | `reports/figures/forecast_next24h.png` |

The commands create their output directories automatically. The forecast
horizon, evaluation windows, boosting rounds, and every input or output path
can also be configured from the command line:

```powershell
python -m src.forecast_24h `
aep-forecast `
--horizon 48 `
--estimators 400 `
--output reports\forecast_next48h.csv `
Expand Down Expand Up @@ -215,7 +218,7 @@ before rendering them.
Custom paths and target columns can be supplied without editing the source:

```powershell
python -m src.make_features `
aep-make-features `
--input data\AEP_hourly.csv `
--output data\features_aep.csv `
--target AEP_MW
Expand Down
16 changes: 8 additions & 8 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,19 +40,19 @@ dev = [
]

[project.scripts]
aep-demo = "src.demo_pipeline:main"
aep-sample-data = "src.sample_data:main"
aep-make-features = "src.make_features:main"
aep-baseline-eval = "src.baseline_eval:main"
aep-xgb-eval = "src.xgb_eval:main"
aep-forecast = "src.forecast_24h:main"
aep-demo = "aep_load_forecasting.demo_pipeline:main"
aep-sample-data = "aep_load_forecasting.sample_data:main"
aep-make-features = "aep_load_forecasting.make_features:main"
aep-baseline-eval = "aep_load_forecasting.baseline_eval:main"
aep-xgb-eval = "aep_load_forecasting.xgb_eval:main"
aep-forecast = "aep_load_forecasting.forecast_24h:main"

[project.urls]
Homepage = "https://github.com/amarakramali/load-forecasting-xgboost"
Issues = "https://github.com/amarakramali/load-forecasting-xgboost/issues"

[tool.setuptools]
packages = ["src"]
[tool.setuptools.packages.find]
where = ["src"]

[tool.ruff]
target-version = "py310"
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
import pandas as pd
from matplotlib.figure import Figure

from src.evaluation import HOURS_PER_DAY, trailing_window
from src.reporting import (
from aep_load_forecasting.evaluation import HOURS_PER_DAY, trailing_window
from aep_load_forecasting.reporting import (
EvaluationResult,
evaluate_predictions,
format_result,
Expand Down
File renamed without changes.
22 changes: 14 additions & 8 deletions src/demo_pipeline.py → src/aep_load_forecasting/demo_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,29 +12,35 @@

import joblib

from src.baseline_eval import (
from aep_load_forecasting.baseline_eval import (
DEFAULT_EVALUATION_DAYS,
DEFAULT_PLOT_DAYS,
evaluate_baselines,
save_baseline_plot,
)
from src.evaluation import HOURS_PER_DAY
from src.forecast_24h import (
from aep_load_forecasting.evaluation import HOURS_PER_DAY
from aep_load_forecasting.forecast_24h import (
DEFAULT_ESTIMATORS,
save_forecast_plot,
train_final_model,
)
from src.forecasting import recursive_forecast
from src.make_features import load_hourly_series, make_feature_table
from src.reporting import save_results
from src.sample_data import (
from aep_load_forecasting.forecasting import recursive_forecast
from aep_load_forecasting.make_features import (
load_hourly_series,
make_feature_table,
)
from aep_load_forecasting.reporting import save_results
from aep_load_forecasting.sample_data import (
DEFAULT_DAYS,
DEFAULT_SEED,
DEFAULT_START,
generate_hourly_load,
save_sample_data,
)
from src.xgb_eval import evaluate_xgboost, save_evaluation_plot
from aep_load_forecasting.xgb_eval import (
evaluate_xgboost,
save_evaluation_plot,
)

DEFAULT_OUTPUT_DIR = Path(".")
DEFAULT_HORIZON = 24
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,15 @@
from matplotlib.figure import Figure
from xgboost import XGBRegressor

from src.forecasting import (
from aep_load_forecasting.forecasting import (
FORECAST_FEATURES,
recursive_forecast,
validate_feature_columns,
)
from src.make_features import DEFAULT_INPUT, load_hourly_series
from aep_load_forecasting.make_features import (
DEFAULT_INPUT,
load_hourly_series,
)

DEFAULT_FEATURES = Path("data") / "features_aep.csv"
DEFAULT_FORECAST = Path("reports") / "forecast_next24h.csv"
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
13 changes: 10 additions & 3 deletions src/xgb_eval.py → src/aep_load_forecasting/xgb_eval.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,16 @@
from matplotlib.figure import Figure
from xgboost import XGBRegressor

from src.evaluation import HOURS_PER_DAY, chronological_split, trailing_window
from src.forecasting import FORECAST_FEATURES, validate_feature_columns
from src.reporting import (
from aep_load_forecasting.evaluation import (
HOURS_PER_DAY,
chronological_split,
trailing_window,
)
from aep_load_forecasting.forecasting import (
FORECAST_FEATURES,
validate_feature_columns,
)
from aep_load_forecasting.reporting import (
EvaluationResult,
evaluate_predictions,
format_result,
Expand Down
2 changes: 1 addition & 1 deletion streamlit_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import streamlit as st

from src.demo_data import (
from aep_load_forecasting.demo_data import (
FORECAST_COLUMN,
ForecastDataError,
forecast_plot_columns,
Expand Down
2 changes: 1 addition & 1 deletion tests/test_baseline_eval.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import pandas as pd
import pytest

from src.baseline_eval import (
from aep_load_forecasting.baseline_eval import (
evaluate_baselines,
load_baseline_features,
main,
Expand Down
2 changes: 1 addition & 1 deletion tests/test_demo_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import pandas as pd
import pytest

from src.demo_data import (
from aep_load_forecasting.demo_data import (
BASELINE_COLUMN,
FORECAST_COLUMN,
ForecastDataError,
Expand Down
4 changes: 2 additions & 2 deletions tests/test_demo_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
import pandas as pd
import pytest

from src.demo_pipeline import main, run_demo_pipeline
from src.forecasting import FORECAST_FEATURES
from aep_load_forecasting.demo_pipeline import main, run_demo_pipeline
from aep_load_forecasting.forecasting import FORECAST_FEATURES


def test_command_writes_complete_reproducible_demo(tmp_path) -> None:
Expand Down
5 changes: 4 additions & 1 deletion tests/test_evaluation.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
import pandas as pd
import pytest

from src.evaluation import chronological_split, trailing_window
from aep_load_forecasting.evaluation import (
chronological_split,
trailing_window,
)


def hourly_frame(periods: int = 2_000) -> pd.DataFrame:
Expand Down
4 changes: 2 additions & 2 deletions tests/test_forecast_24h.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

import pandas as pd

import src.forecast_24h as command
from src.forecasting import FORECAST_FEATURES
import aep_load_forecasting.forecast_24h as command
from aep_load_forecasting.forecasting import FORECAST_FEATURES


def test_command_forwards_estimator_count_and_writes_outputs(
Expand Down
2 changes: 1 addition & 1 deletion tests/test_forecasting.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import pandas as pd
import pytest

from src.forecasting import (
from aep_load_forecasting.forecasting import (
FORECAST_FEATURES,
make_forecast_row,
recursive_forecast,
Expand Down
6 changes: 5 additions & 1 deletion tests/test_make_features.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@
import pandas as pd
import pytest

from src.make_features import build_features, load_hourly_series, make_feature_table
from aep_load_forecasting.make_features import (
build_features,
load_hourly_series,
make_feature_table,
)


def synthetic_load(periods: int = 200) -> pd.Series:
Expand Down
2 changes: 1 addition & 1 deletion tests/test_reporting.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import pandas as pd
import pytest

from src.reporting import (
from aep_load_forecasting.reporting import (
EvaluationResult,
evaluate_predictions,
format_result,
Expand Down
7 changes: 5 additions & 2 deletions tests/test_sample_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@
import pandas as pd
import pytest

from src.make_features import build_features, load_hourly_series
from src.sample_data import generate_hourly_load, main
from aep_load_forecasting.make_features import (
build_features,
load_hourly_series,
)
from aep_load_forecasting.sample_data import generate_hourly_load, main


def test_generate_hourly_load_is_deterministic_and_hourly() -> None:
Expand Down
4 changes: 2 additions & 2 deletions tests/test_xgb_eval.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
import pandas as pd
import pytest

from src.forecasting import FORECAST_FEATURES
from src.xgb_eval import (
from aep_load_forecasting.forecasting import FORECAST_FEATURES
from aep_load_forecasting.xgb_eval import (
evaluate_xgboost,
load_evaluation_features,
main,
Expand Down
Loading