Changepoints¶
Use model_components.changepoints
to specify the changepoints.
Silverkite¶
There are two type of changepoints that are allowed in Silverkite: (trend) changepoints and seasonality changepoints. Changepoints allow you to specify changes in the growth rate. Together, growth and changepoints define the overall trend. Seasonality changepoints allow you to specify changes in the seasonality shapes.
A quickstart example for automatic trend changepoint detection in Silverkite can be found at Changepoint Detection
By default, there are no changepoints.
Trend changepoint options:
changepoints : `dict` [`str`, `dict`] or None
Specifies the changepoint configuration. Dictionary with the following
optional key:
``"changepoints_dict"`` : `dict` or None or a list of such values for grid search
changepoints dictionary passed to ``forecast_simple_silverkite``. A dictionary
with the following optional keys:
``"method"`` : `str`
The method to locate changepoints. Valid options:
"uniform". Places n_changepoints evenly spaced changepoints to allow growth to change.
"custom". Places changepoints at the specified dates.
"auto". Automatically detects change points.
Additional keys to provide parameters for each particular method are described below.
``"continuous_time_col"`` : `str` or None
Column to apply `growth_func` to, to generate changepoint features
Typically, this should match the growth term in the model
``"growth_func"`` : callable or None
Growth function (`numeric` -> `numeric`). Changepoint features are created
by applying `growth_func` to "continuous_time_col" with offsets.
If None, uses identity function to use `continuous_time_col` directly
as growth term
If changepoints_dict["method"] == "uniform", this other key is required:
``"n_changepoints"`` : `int`
number of changepoints to evenly space across training period
If changepoints_dict["method"] == "custom", this other key is required:
``"dates"`` : `list` [`int` or `float` or `str` or `datetime`]
Changepoint dates. Must be parsable by pd.to_datetime.
Changepoints are set at the closest time on or after these dates
in the dataset.
If changepoints_dict["method"] == "auto", optional keys can be passed that match the parameters in
`~greykite.algo.changepoint.adalasso.changepoint_detector.ChangepointDetector.find_trend_changepoints`
(except ``df``, ``time_col`` and ``value_col``, which are already known).
To add manually specified changepoints to the automatically detected ones, the keys ``dates``,
``combine_changepoint_min_distance`` and ``keep_detected`` can be specified, which correspond to the
three parameters ``custom_changepoint_dates``, ``min_distance`` and ``keep_detected`` in
`~greykite.algo.changepoint.adalasso.changepoints_utils.combine_detected_and_custom_trend_changepoints`.
Examples:
# Places three changepoints uniformly within each training set.
# Piecewise linear growth is defined by `continuous_time_col` and `growth_func`
changepoints = dict(
changepoints_dict=dict(
method="uniform",
n_changepoints=3,
continuous_time_col="ct1", # the default, no need to specify
growth_func=lambda x: x # the default, no need to specify
)
)
# Piecewise linear growth, two changepoints at specific dates
# If a date is not contained in one of the training splits,
# it is ignored for that split.
# `dates`: Iterable[Union[int, float, str, datetime]], interpreted by pd.to_datetime
changepoints = dict(
changepoints_dict=dict(
method="custom",
dates=["2018-08-01", "2019-08-03"]
)
)
# Grid search is possible
changepoints = dict(
changepoints_dict=[
dict(
method="custom",
dates=["2018-08-01", "2019-08-03"]
),
dict(
method="custom",
dates=["2019-08-03"]
),
]
)
# Automatic change point detection
changepoints=dict(
changepoints_dict=dict(
method="auto",
regularization_strength=0.6,
resample_freq="7D",
actual_changepoint_min_distance="100D",
potential_changepoint_distance="50D",
no_changepoint_proportion_from_end=0.3,
yearly_seasonality_order=6,
# Manually specifies two changepoints to add
dates=["2007-12-01", "2009-06-01"],
combine_changepoint_min_distance="100D", # Default is `actual_changepoint_min_distance` if not specified
keep_detected=False, # Prefers manual changepoints over detected ones in case of overlap
)
)
To place changepoints, plot the overall trend over time, and see if you can identify
places where the trend changes noticeably. If so, use method="custom"
and
add a changepoint at those dates. If not, try a few uniform changepoints, and
see if your backtest error improves.
Note
Do not place a changepoint too close to the end of your dataset, because it may not have enough data points to learn the new trend.
As a rule of thumb, the last changepoint should have enough training data following it to validate the forecast accuracy in a backtest. For example, for daily data and a a forecast horizon of a few months, reserve 2 months after the last changepoint.
In automatic change point detection, this can be avoided by specifying
no_changepoint_proportion_from_end
or no_changepoint_distance_from_end
.
Note
Changepoints add flexibility to your model. As a rule of thumb, if you do not use the “auto” option for “method”, use at most 3 changepoints, fewer if you include interactions with changepoints.
If you have yearly seasonality in your model and just one year of training data, a very flexible trend can become conflated with the yearly seasonality.
Unlike Prophet, the current implementation of Silverkite does not explicitly encourage smoothness how changepoints affect the growth rate. So it’s best not to have too many.
Custom growth¶
Changepoints also allow you to customize the growth rate as a function of time.
How it works¶
Each changepoint as introduces a regressor whose value is 0 before the changepoint, and
whose value after the changepoint is defined by continuous_time_col
and growth_func
.
Let \(g(t)\) be the value of continuous_time_col at \(t\). A changepoint at date \(t_0\) adds a regressor defined by:
\(\text{growth_func}(g(t)-g(t_0))\) if \(t >= t_0\)
\(0\) otherwise.
For linear growth (continuous_time_col="ct1"
), this is simply:
\(\text{growth_func}(t-t_0)\) if \(t >= t_0\)
\(0\) otherwise.
(\(t-t_0\) is the fractional years since \(t_0\).)
For most applications, you can set continuous_time_col="ct1"
and define
growth_func
to model type of curve you want with time.
Note
Every changepoint date must be a date in your dataset. (The changepoint dates are mapped to the first date on or after the requested date within each training split, and deduped).
growth_func¶
By leveraging growth_func
, you can specify the growth as any function of time.
For example, if you believe the growth rate should be logistic
(wikipedia), and have some domain
knowledge about the growth rate, capacity, and inflection point:
from greykite.common.features.timeseries_features import get_logistic_func
# Defines f(continuous_time_col) =
# floor + capacity / (1 + exp(-growth_rate * (continuous_time_col - inflection_point)))
logistic_func = get_logistic_func(
growth_rate=0.5, # how fast the values go from floor to capacity
capacity=2000.0, # in units of the timeseries value
floor=0.0, # in units of the timeseries value
inflection_point=1.0) # in units of continuous_time_col. How far after the changepoint to place the inflection point
changepoints=dict(
changepoints_dict=dict(
method="custom",
dates=["2018-09-01"], # The dates where continuous_time_col=0 for each logistic curve.
# Placing multiple dates results in multiple logistic curves.
continuous_time_col="ct1",
growth_func=logistic_func
)
)
Tip
If you place a changepoint at your train start date, you can define a custom growth term beyond those available in Growth.
Make sure to set growth=dict(growth_term=None)
for Growth so you have a single
growth term.
Tip
It’s best to have growth_func(0.0) = 0.0
for continuity at the changepoints.
For logistic growth, this means floor
should be 0.0 and inflection_point
should be large.
continuous_time_col¶
continuous_time_col
is a numeric representation of time. You can use this parameter to specify
non-linear growth rate without writing your own growth_func
.
If you specify growth_func
, you will most likely leave this as the
default (linear time "ct1"
).
Here are the options:
continuous_time_col |
description |
---|---|
ct1 |
linear growth, -infinity to infinity |
ct2 |
signed quadratic growth, -infinity to infinity |
ct3 |
signed cubic growth, -infinity to infinity |
ct_sqrt |
signed square root growth, -infinity to infinity |
ct_root3 |
signed cubic root growth, -infinity to infinity |
Note
What is signed growth?
signed growth at
x
is defined by:np.sign(x) * np.power(np.abs(x), pow)
.For example, signed square root is this function with
pow=0.5
.These functions are monotonically increasing with time, making them useful to model growth
For each changepoint, signed growth is calculated on the fractional years since the changepoint date.
The growth_term
specified at Growth
maps to continuous_time_col
as follows:
growth_term |
continuous_time_col |
---|---|
linear |
ct1 |
quadratic |
ct2 |
sqrt |
ct_sqrt |
Other usage¶
Note
By interacting trend and seasonality, you can use changepoints to specify changes in seasonality (e.g increasing over time).
Seasonality changepoints¶
Seasonality changepoints allow seasonality shapes to change at every component level. For example, the shape of yearly seasonality may change at a seasonality changepoint for yearly component, but the shape of seasonality for other components such as weekly or daily will not change at that point. These changepoints can be automatically detected.
You may specify seasonality_changepoints_dict
by itself or along with changepoints_dict
in
model_components.changepoints
. Optional keys of seasonality_changepoints_dict
include keys
in find_seasonality_changepoints
,
except df
, time_col
, value_col
and trend_changepoints
, which will be automatically passed
within the algorithm.
Examples:
# Includes seasonality changepoints with trend changepoints.
# The detected trend changepoints will be used as partial information
# when detecting seasonality changepoints.
# Both are used in the forecast model.
changepoints = dict(
changepoints_dict=dict(
method="auto",
no_changepoint_distance_from_end="180D"
),
seasonality_changepoints_dict=dict(
no_changepoint_distance_from_end="180D"
)
)
# Includes seasonality changepoints only.
# Trend changepoints detection will be triggered and used as partial information
# when detecting seasonality changepoints, but will not be used in the forecast model.
changepoints = dict(
seasonality_changepoints_dict=dict(
potential_changepoint_distance="30D",
seasonality_components_df=pd.DataFrame({
"name": ["tow", "conti_year"],
"period": [7.0, 1.0],
"order": [4, 6],
"seas_names": ["weekly", "yearly"]})
)
)
# Grid search is possible
changepoints = dict(
changepoints_dict=[
dict(
method="custom",
dates=["2018-08-01", "2019-08-03"]
),
dict(
method="custom",
dates=["2019-08-03"]
),
],
seasonality_changepoints_dict=[
dict(), # an empty dictionary triggers seasonality changepoints detection with default parameters
dict(
regularization_strength=0.4
)
]
)
Note
Similar to (trend) changepoints, placing seasonality changepoints too close to the end of data is not
recommended. It’s important to specify the parameter no_changepoint_distance_from_end
or
no_changepoint_proportion_from_end
, where the former overrides the latter.
Prophet¶
By default, there are 25 uniformly spaced changepoints.
Options:
changepoints : `dict` [`str`, `any`] or None
Specifies the changepoint configuration. Dictionary with the following optional keys:
changepoint_prior_scale : `float` or None or list of such values for grid search, default 0.05
Parameter modulating the flexibility of the automatic changepoint selection.
0.05 by default.
Large values will allow many changepoints, small values will allow few changepoints.
changepoints : `list` [`datetime.datetime`] or None or list of such values for grid search, default None
List of dates at which to include potential changepoints. None by default, if not specified,
potential changepoints are selected automatically.
n_changepoints : `int` or None or list of such values for grid search, default 25
Number of potential changepoints to include. Not used if input `changepoints` is supplied.
If `changepoints` is not supplied, then n_changepoints potential changepoints are selected uniformly from
the first `changepoint_range` proportion of the history.
changepoint_range : `float` or None or list of such values for grid search, default 0.8
Proportion of history in which trend changepoints will be estimated. Permitted values: (0,1]
Not used if input `changepoints` is supplied.
Examples:
# Places specific changepoints to model piecewise linear growth.
# If a date is not contained in one of the training splits, it is ignored for that split.
changepoints = dict(
changepoints=['2019-01-01', '2019-05-01', '2019-07-01']
)
# Specifies number of changepoints and % of training data used to place these points.
# Grid search example.
changepoints = dict(
n_changepoints=[20, 30],
changepoint_range=[0.7, 0.8]
)
# Modulates flexibility of trend fit
changepoints = dict(
changepoint_prior_scale=[0.04, 0.1, 0.5],
n_changepoints=[20, 30],
changepoint_range=[0.7, 0.8]
)
Note
Follow the same guidance as Silverkite for changepoints, with one key difference: n_changepoints
can be higher for Prophet than for Silverkite.
As a Bayesian model, Prophet uses changepoint_prior_scale
to limit flexibility,
even if the number of changepoints is high.
To further improve model fit, you may try different n_changepoints
and
increasing changepoint_range
. However, make sure to reserve enough data after the
last changepoint to learn the new trend and validate the forecast accuracy,
as explained for Silverkite above.
If changepoint_range
is too high, seasonality effects can be mistaken for trend,
and the forecast will not be accurate. On the other hand, because changepoint_range
is determined as a fraction of the input dataset,
it can be safe to increase if you have many training points.
For example, for 3 years of daily data, changepoint_range=0.8
reserves 7.2 months after the
last changepoint. If you believe the trend changed in those last 7.2 months,
then you can try increasing changepoint_range
while reserving data for validation.
Note
It is possible to fine tune changepoint fit using changepoint_prior_scale
.
You can fix overfit (too much flexibility) or underfit (not enough flexibility) using this parameter.
There is no standard threshold. Optimum value depends on underlying data.
Try different values in grid search to find a good fit via cross validation. As a general rule, choose final model which has lower test MAPE than other models to eliminate overfitting risk. e.g. you may try [0.01, 0.05, 0.25].