Skip to content

Commit

Permalink
Merge pull request #33 from NREL/release/1.2.1
Browse files Browse the repository at this point in the history
fixes for ngboost imports; bump version to 1.2.1
  • Loading branch information
nreinicke authored Jan 29, 2025
2 parents f6f88a4 + 3c05b9c commit 2b1cfe6
Show file tree
Hide file tree
Showing 14 changed files with 151 additions and 47 deletions.
15 changes: 10 additions & 5 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
python-version: ["3.9", "3.10", "3.11"]

env:
PYTHON: ${{ matrix.python-version }}
Expand All @@ -38,12 +38,17 @@ jobs:
pip install ".[dev]"
- name: Run mypy
run: mypy .
run: mypy .

- name: Run black
- name: Lint with ruff
run: |
black nrel tests --check
ruff check
- name: ruff format check
run: |
ruff format --check
- name: Python unit tests
run: |
pytest tests/ -v
python -m unittest discover tests/
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# <img src="docs/images/routeelogo.png" alt="Routee Powertrain" width="100"/>

<div align="left">
<img src="https://img.shields.io/badge/python-3.8%20%7C%203.9%20%7C%203.10%20%7C%203.11-blue"/>
<img src="https://img.shields.io/badge/python-3.9%20%7C%203.10%20%7C%203.11-blue"/>
<a href="https://pypi.org/project/nrel.routee.powertrain/">
<img src="https://img.shields.io/pypi/v/nrel.routee.powertrain" alt="PyPi Latest Release"/>
</a>
Expand Down
10 changes: 1 addition & 9 deletions docs/model_training-ngboost.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,10 @@
"import nrel.routee.powertrain as pt\n",
"\n",
"from nrel.routee.powertrain.trainers.ngboost_trainer import NGBoostTrainer\n",
"from nrel.routee.powertrain.trainers.sklearn_random_forest import SklearnRandomForestTrainer\n",
"\n",
"from nrel.routee.powertrain.estimators.ngboost_estimator import NGBoostEstimator\n",
"\n",
"import pandas as pd\n",
"import numpy as np\n",
"import matplotlib.pyplot as plt\n",
"import time\n",
"\n",
"from sklearn.model_selection import train_test_split\n",
"from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score\n"
"\n"
]
},
{
Expand All @@ -53,7 +46,6 @@
"metadata": {},
"outputs": [],
"source": [
"import pandas as pd\n",
"\n",
"df = pd.read_csv(\"../tests/routee-powertrain-test-data/sample_train_data.csv\")"
]
Expand Down
1 change: 1 addition & 0 deletions nrel/routee/powertrain/__about__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__version__ = "1.2.1"
17 changes: 15 additions & 2 deletions nrel/routee/powertrain/__init__.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,28 @@
import logging
from pathlib import Path

__all__ = [
"DataColumn",
"FeatureSet",
"Constraints",
"TargetSet",
"Model",
"ModelConfig",
"PowertrainType",
"list_available_models",
"load_model",
"load_sample_route",
"visualize_features",
"contour_plot",
]

from .core.features import DataColumn, FeatureSet, Constraints, TargetSet
from .core.model import Model
from .core.model_config import ModelConfig
from .core.powertrain_type import PowertrainType
from .io.load import list_available_models, load_model, load_sample_route
from .validation.feature_visualization import visualize_features, contour_plot

__version__ = "1.0.0"

log = logging.getLogger()
log.setLevel(logging.INFO)

Expand Down
6 changes: 5 additions & 1 deletion nrel/routee/powertrain/core/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,11 @@ def to_json(self) -> str:
@classmethod
def from_dict(cls, d: dict) -> Metadata:
v = get_version()
if d["routee_version"] != v:
major_v = v.split(".")[0]

incoming_v = d["routee_version"]
incoming_major_v = incoming_v.split(".")[0]
if incoming_major_v != major_v:
warnings.warn(
"this model was trained using routee-powertrain version "
f"{d['routee_version']} but you're using version {v}"
Expand Down
2 changes: 1 addition & 1 deletion nrel/routee/powertrain/core/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,7 @@ def __repr__(self) -> str:
for feature in feature_set.features:
summary_lines.append(f"Feature: {feature.name} ({feature.units})")
summary_lines.append(
f"Distance: {config.distance.name} " f"({config.distance.units})"
f"Distance: {config.distance.name} ({config.distance.units})"
)
for target in config.target.targets:
summary_lines.append(f"Target: {target.name} ({target.units})")
Expand Down
5 changes: 3 additions & 2 deletions nrel/routee/powertrain/core/model_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,9 @@ def __post_init__(self):
feature_set_ids = [f.features_id for f in self.feature_sets]
if len(feature_set_ids) != len(set(feature_set_ids)):
raise ValueError(
"Feature sets must have unique ids. "
"Found duplicate ids: {}".format(feature_set_ids)
"Feature sets must have unique ids. Found duplicate ids: {}".format(
feature_set_ids
)
)

# now check all the types
Expand Down
28 changes: 23 additions & 5 deletions nrel/routee/powertrain/estimators/ngboost_estimator.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
from __future__ import annotations

# from abc import ABC, abstractmethod
from pathlib import Path
import joblib
import base64
import io
import json
import pandas as pd
from ngboost import NGBRegressor

from importlib.util import find_spec

from nrel.routee.powertrain.core.features import DataColumn, FeatureSet, TargetSet
from nrel.routee.powertrain.core.model_config import PredictMethod
from nrel.routee.powertrain.estimators.estimator_interface import Estimator


class NGBoostEstimator(Estimator):

def __init__(self, ngboost) -> None:
self.model = ngboost

Expand All @@ -41,10 +39,23 @@ def to_file(self, filepath: str | Path):
json.dump(self.to_dict(), f)

@classmethod
def from_dict(cls, in_dict: dict) -> "NGBRegressor":
def from_dict(cls, in_dict: dict) -> NGBoostEstimator:
"""
Load an estimator from a bytes object in memory
"""
if find_spec("ngboost") is None:
raise ImportError(
"The NGBoostEstimator estimator requires extra dependencies like joblib and ngboost. "
"To install, you can do pip install nrel.routee.powertrain[ngboost]"
)

if find_spec("joblib") is None:
raise ImportError(
"The NGBoostEstimator estimator requires extra dependencies like joblib and ngboost. "
"To install, you can do pip install nrel.routee.powertrain[ngboost]"
)
else:
import joblib

model_base64 = in_dict.get("ngboost_model")

Expand All @@ -60,6 +71,13 @@ def to_dict(self) -> dict:
"""
Serialize an estimator to a python dictionary
"""
try:
import joblib
except ImportError:
raise ImportError(
"The NGBoostEstimator estimator requires extra dependencies like joblib and ngboost. "
"To install, you can do pip install nrel.routee.powertrain[ngboost]"
)
byte_stream = io.BytesIO()
joblib.dump(self.model, byte_stream)
byte_stream.seek(0)
Expand Down
2 changes: 2 additions & 0 deletions nrel/routee/powertrain/estimators/sklearn/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
from .estimator import SKLearnEstimator

__all__ = ["SKLearnEstimator"]
4 changes: 1 addition & 3 deletions nrel/routee/powertrain/trainers/ngboost_trainer.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
from enum import Enum
import pandas as pd

from ngboost import NGBRegressor
from ngboost.distns import Exponential, Normal
from ngboost.distns import Normal


from nrel.routee.powertrain.core.model_config import ModelConfig
Expand All @@ -21,7 +20,6 @@ def __init__(
learning_rate: float = 0.01,
random_state: int = 52,
):

self.n_estimators = n_estimators
self.dist = dist
self.verbose = verbose
Expand Down
2 changes: 1 addition & 1 deletion nrel/routee/powertrain/utils/fs.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def read(path: Path):
return fp.read()


def get_version(path: Path = root() / "__init__.py"):
def get_version(path: Path = root() / "__about__.py"):
with path.open("r") as fp:
for line in fp.readlines():
if line.startswith("__version__"):
Expand Down
22 changes: 21 additions & 1 deletion nrel/routee/powertrain/validation/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

import numpy as np
import pandas as pd
from scipy.stats import norm

from nrel.routee.powertrain.core.features import FeatureSetId
from nrel.routee.powertrain.core.model_config import ModelConfig
Expand Down Expand Up @@ -85,6 +84,13 @@ def calculate_nll(target, target_pred, target_std) -> float:
"""
Calculate Negative Log-Likelihood (NLL).
"""
try:
from scipy.stats import norm
except ImportError:
raise ImportError(
"The calculate_nll function requires scipy. "
"To install, you can do pip install scipy"
)
nll = -np.mean(norm.logpdf(target, loc=target_pred, scale=target_std))
return nll

Expand All @@ -93,6 +99,13 @@ def calculate_crps(target, target_pred, target_std) -> float:
"""
Calculate Continuous Ranked Probability Score (CRPS).
"""
try:
from scipy.stats import norm
except ImportError:
raise ImportError(
"The calculate_nll function requires scipy. "
"To install, you can do pip install scipy"
)
# CDF of the predicted distribution
z = (target - target_pred) / target_std
crps = target_std * (
Expand Down Expand Up @@ -344,6 +357,13 @@ def compute_errors(
)

if isinstance(estimator, NGBoostEstimator):
try:
from scipy.stats import norm
except ImportError:
raise ImportError(
"The errors for the NGBoostEstimator requires other dependnecies like scipy. "
"To install, you can do `pip install nrel.routee.powertrain[ngboost]"
)
target_std = np.array(predictions[energy_name + "_std"])
alpha = 0.05
z = norm.ppf(1 - alpha / 2) # z-score for 95% confidence
Expand Down
82 changes: 66 additions & 16 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
[build-system]
requires = ["setuptools>=63.0.0", "wheel"]
build-backend = "setuptools.build_meta"
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "nrel.routee.powertrain"
version = "1.2.0"
dynamic = ["version"]
description = "RouteE-Powertrain is a tool for predicting energy usage over a set of road links."
readme = "README.md"
authors = [{ name = "National Renewable Energy Laboratory" }]
Expand All @@ -18,22 +18,22 @@ classifiers = [
"Topic :: Scientific/Engineering",
]
dependencies = ["pandas", "numpy", "onnx", "onnxruntime==1.18.1"]
requires-python = ">=3.8"
requires-python = ">=3.9"

[project.optional-dependencies]
scikit = ["scikit-learn", "skl2onnx"]
ngboost = ["ngboost"]
scikit = ["scikit-learn==1.2.2", "skl2onnx"]
ngboost = ["ngboost==0.5.2", "scikit-learn==1.2.2"]
plot = ["matplotlib"]
dev = [
"nrel.routee.powertrain[scikit]",
"nrel.routee.powertrain[plot]",
"nrel.routee.powertrain[ngboost]",
"tqdm",
"pytest",
"black",
"mypy",
"maturin",
"ruff",
"hatch",
"shapely",
"boxsdk",
"jupyter-book",
Expand All @@ -49,20 +49,70 @@ dev = [
[project.urls]
Homepage = "https://github.com/NREL/routee-powertrain"

[tool.setuptools.packages.find]
where = ["."] # list of folders that contain the packages (["."] by default)
include = [
"nrel*",
] # package names should match these glob patterns (["*"] by default)
[tool.hatch.version]
path = "nrel/routee/powertrain/__about__.py"

[tool.setuptools.package-data]
"nrel.routee.powertrain" = ["py.typed"]
[tool.hatch.build.targets.sdist]
exclude = ["scripts/", "tests/", "docs/"]

[tool.ruff.per-file-ignores]
"__init__.py" = ["F401"]
[tool.hatch.build.targets.wheel]
packages = ["nrel/routee/powertrain"]

[tool.mypy]
ignore_missing_imports = true
namespace_packages = true
explicit_package_bases = true
exclude = ["docs/", "build/", "dist/"]

[tool.ruff]
include = ["nrel/**/*.py", "tests/*.py"]
# Exclude a variety of commonly ignored directories.
exclude = [
".bzr",
".direnv",
".eggs",
".git",
".git-rewrite",
".hg",
".ipynb_checkpoints",
".mypy_cache",
".nox",
".pants.d",
".pyenv",
".pytest_cache",
".pytype",
".ruff_cache",
".svn",
".tox",
".venv",
".vscode",
"__pypackages__",
"_build",
"buck-out",
"build",
"dist",
"node_modules",
"site-packages",
"venv",
]

# Same as Black.
line-length = 88
indent-width = 4

# Assume Python 3.9
target-version = "py39"

[tool.ruff.lint]
# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default.
# Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or
# McCabe complexity (`C901`) by default.
select = ["E4", "E7", "E9", "F"]
ignore = []

# Allow fix for all enabled rules (when `--fix`) is provided.
fixable = ["ALL"]
unfixable = []

# Allow unused variables when underscore-prefixed.
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"

0 comments on commit 2b1cfe6

Please sign in to comment.