Add OrderHistory.choose_tactical_model()
- the method implements a heuristic from the first research paper that chooses the most promising forecasting `*Model` based on the average daily demand in a `Pixel` for a given `train_horizon` - adjust the test scenario => `LONG_TRAIN_HORIZON` becomes `8` as that is part of the rule implemented in the heuristic
This commit is contained in:
parent
8926e9ff28
commit
af82951485
6 changed files with 199 additions and 35 deletions
|
|
@ -1,37 +1,145 @@
|
|||
"""Tests for the `OrderHistory.avg_daily_demand()` method."""
|
||||
"""Tests for the `OrderHistory.avg_daily_demand()` and ...
|
||||
|
||||
`OrderHistory.choose_tactical_model()` methods.
|
||||
|
||||
We test both methods together as they take the same input and are really
|
||||
two parts of the same conceptual step.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from tests import config as test_config
|
||||
from urban_meal_delivery.forecasts import models
|
||||
|
||||
|
||||
def test_avg_daily_demand_with_constant_demand(
|
||||
order_history, good_pixel_id, predict_at,
|
||||
):
|
||||
"""The average daily demand must be the number of time steps ...
|
||||
class TestAverageDailyDemand:
|
||||
"""Tests for the `OrderHistory.avg_daily_demand()` method."""
|
||||
|
||||
... if the demand is `1` at each time step.
|
||||
def test_avg_daily_demand_with_constant_demand(
|
||||
self, order_history, good_pixel_id, predict_at,
|
||||
):
|
||||
"""The average daily demand must be the number of time steps ...
|
||||
|
||||
Note: The `order_history` fixture assumes `12` time steps per day as it
|
||||
uses `LONG_TIME_STEP=60` as the length of a time step.
|
||||
"""
|
||||
result = order_history.avg_daily_demand(
|
||||
pixel_id=good_pixel_id,
|
||||
predict_day=predict_at.date(),
|
||||
train_horizon=test_config.LONG_TRAIN_HORIZON,
|
||||
)
|
||||
... if the demand is `1` at each time step.
|
||||
|
||||
assert result == 12.0
|
||||
Note: The `order_history` fixture assumes `12` time steps per day as it
|
||||
uses `LONG_TIME_STEP=60` as the length of a time step.
|
||||
"""
|
||||
result = order_history.avg_daily_demand(
|
||||
pixel_id=good_pixel_id,
|
||||
predict_day=predict_at.date(),
|
||||
train_horizon=test_config.LONG_TRAIN_HORIZON,
|
||||
)
|
||||
|
||||
assert result == 12.0
|
||||
|
||||
def test_avg_daily_demand_with_no_demand(
|
||||
self, order_history, good_pixel_id, predict_at,
|
||||
):
|
||||
"""Without demand, the average daily demand must be `0.0`."""
|
||||
order_history._data.loc[:, 'n_orders'] = 0
|
||||
|
||||
result = order_history.avg_daily_demand(
|
||||
pixel_id=good_pixel_id,
|
||||
predict_day=predict_at.date(),
|
||||
train_horizon=test_config.LONG_TRAIN_HORIZON,
|
||||
)
|
||||
|
||||
assert result == 0.0
|
||||
|
||||
|
||||
def test_avg_daily_demand_with_no_demand(
|
||||
order_history, good_pixel_id, predict_at,
|
||||
):
|
||||
"""Without demand, the average daily demand must be `0.0`."""
|
||||
order_history._data.loc[:, 'n_orders'] = 0
|
||||
class TestChooseTacticalModel:
|
||||
"""Tests for the `OrderHistory.choose_tactical_model()` method."""
|
||||
|
||||
result = order_history.avg_daily_demand(
|
||||
pixel_id=good_pixel_id,
|
||||
predict_day=predict_at.date(),
|
||||
train_horizon=test_config.LONG_TRAIN_HORIZON,
|
||||
)
|
||||
def test_best_model_with_high_demand(
|
||||
self, order_history, good_pixel_id, predict_at,
|
||||
):
|
||||
"""With high demand, the average daily demand is `.>= 25.0`."""
|
||||
# With 12 time steps per day, the ADD becomes `36.0`.
|
||||
order_history._data.loc[:, 'n_orders'] = 3
|
||||
|
||||
assert result == 0.0
|
||||
result = order_history.choose_tactical_model(
|
||||
pixel_id=good_pixel_id,
|
||||
predict_day=predict_at.date(),
|
||||
train_horizon=test_config.LONG_TRAIN_HORIZON,
|
||||
)
|
||||
|
||||
assert isinstance(result, models.HorizontalETSModel)
|
||||
|
||||
def test_best_model_with_medium_demand(
|
||||
self, order_history, good_pixel_id, predict_at,
|
||||
):
|
||||
"""With medium demand, the average daily demand is `>= 10.0` and `< 25.0`."""
|
||||
# With 12 time steps per day, the ADD becomes `24.0`.
|
||||
order_history._data.loc[:, 'n_orders'] = 2
|
||||
|
||||
result = order_history.choose_tactical_model(
|
||||
pixel_id=good_pixel_id,
|
||||
predict_day=predict_at.date(),
|
||||
train_horizon=test_config.LONG_TRAIN_HORIZON,
|
||||
)
|
||||
|
||||
assert isinstance(result, models.HorizontalETSModel)
|
||||
|
||||
def test_best_model_with_low_demand(
|
||||
self, order_history, good_pixel_id, predict_at,
|
||||
):
|
||||
"""With low demand, the average daily demand is `>= 2.5` and `< 10.0`."""
|
||||
# With 12 time steps per day, the ADD becomes `12.0` ...
|
||||
data = order_history._data
|
||||
data.loc[:, 'n_orders'] = 1
|
||||
|
||||
# ... and we set three additional time steps per day to `0`.
|
||||
data.loc[ # noqa:ECE001
|
||||
# all `Pixel`s, all `Order`s in time steps starting at 11 am
|
||||
(slice(None), slice(data.index.levels[1][0], None, 12)),
|
||||
'n_orders',
|
||||
] = 0
|
||||
data.loc[ # noqa:ECE001
|
||||
# all `Pixel`s, all `Order`s in time steps starting at 12 am
|
||||
(slice(None), slice(data.index.levels[1][1], None, 12)),
|
||||
'n_orders',
|
||||
] = 0
|
||||
data.loc[ # noqa:ECE001
|
||||
# all `Pixel`s, all `Order`s in time steps starting at 1 pm
|
||||
(slice(None), slice(data.index.levels[1][2], None, 12)),
|
||||
'n_orders',
|
||||
] = 0
|
||||
|
||||
result = order_history.choose_tactical_model(
|
||||
pixel_id=good_pixel_id,
|
||||
predict_day=predict_at.date(),
|
||||
train_horizon=test_config.LONG_TRAIN_HORIZON,
|
||||
)
|
||||
|
||||
# TODO: this should be the future `HorizontalSMAModel`.
|
||||
assert isinstance(result, models.HorizontalETSModel)
|
||||
|
||||
def test_best_model_with_no_demand(
|
||||
self, order_history, good_pixel_id, predict_at,
|
||||
):
|
||||
"""Without demand, the average daily demand is `< 2.5`."""
|
||||
order_history._data.loc[:, 'n_orders'] = 0
|
||||
|
||||
result = order_history.choose_tactical_model(
|
||||
pixel_id=good_pixel_id,
|
||||
predict_day=predict_at.date(),
|
||||
train_horizon=test_config.LONG_TRAIN_HORIZON,
|
||||
)
|
||||
|
||||
# TODO: this should be the future `HorizontalTrivialModel`.
|
||||
assert isinstance(result, models.HorizontalETSModel)
|
||||
|
||||
def test_best_model_for_unknown_train_horizon(
|
||||
self, order_history, good_pixel_id, predict_at, # noqa:RST215
|
||||
):
|
||||
"""For `train_horizon`s not included in the rule-based system ...
|
||||
|
||||
... the method raises a `RuntimeError`.
|
||||
"""
|
||||
with pytest.raises(RuntimeError, match='no rule'):
|
||||
order_history.choose_tactical_model(
|
||||
pixel_id=good_pixel_id,
|
||||
predict_day=predict_at.date(),
|
||||
train_horizon=test_config.SHORT_TRAIN_HORIZON,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ def bad_predict_at():
|
|||
... not a long enough history so that both `SHORT_TRAIN_HORIZON`
|
||||
and `LONG_TRAIN_HORIZON` do not work.
|
||||
"""
|
||||
predict_day = test_config.END - datetime.timedelta(weeks=2, days=1)
|
||||
predict_day = test_config.END - datetime.timedelta(weeks=6, days=1)
|
||||
return datetime.datetime(
|
||||
predict_day.year, predict_day.month, predict_day.day, test_config.NOON, 0,
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue