mirror of
https://github.com/saymrwulf/prophet.git
synced 2026-07-29 20:14:08 +00:00
Style modifications to fbprophet
This commit is contained in:
parent
83f66dadfd
commit
3a6a338ac2
3 changed files with 86 additions and 88 deletions
|
|
@ -144,17 +144,17 @@ class Prophet(object):
|
|||
"""Validates the inputs to Prophet."""
|
||||
if self.growth not in ('linear', 'logistic'):
|
||||
raise ValueError(
|
||||
"Parameter 'growth' should be 'linear' or 'logistic'.")
|
||||
'Parameter "growth" should be "linear" or "logistic".')
|
||||
if ((self.changepoint_range < 0) or (self.changepoint_range > 1)):
|
||||
raise ValueError("Parameter 'changepoint_range' must be in [0, 1]")
|
||||
raise ValueError('Parameter "changepoint_range" must be in [0, 1]')
|
||||
if self.holidays is not None:
|
||||
if not (
|
||||
isinstance(self.holidays, pd.DataFrame)
|
||||
and 'ds' in self.holidays # noqa W503
|
||||
and 'holiday' in self.holidays # noqa W503
|
||||
):
|
||||
raise ValueError("holidays must be a DataFrame with 'ds' and "
|
||||
"'holiday' columns.")
|
||||
raise ValueError('holidays must be a DataFrame with "ds" and '
|
||||
'"holiday" columns.')
|
||||
self.holidays['ds'] = pd.to_datetime(self.holidays['ds'])
|
||||
has_lower = 'lower_window' in self.holidays
|
||||
has_upper = 'upper_window' in self.holidays
|
||||
|
|
@ -170,7 +170,7 @@ class Prophet(object):
|
|||
self.validate_column_name(h, check_holidays=False)
|
||||
if self.seasonality_mode not in ['additive', 'multiplicative']:
|
||||
raise ValueError(
|
||||
"seasonality_mode must be 'additive' or 'multiplicative'"
|
||||
'seasonality_mode must be "additive" or "multiplicative"'
|
||||
)
|
||||
|
||||
def validate_column_name(self, name, check_holidays=True,
|
||||
|
|
@ -198,21 +198,21 @@ class Prophet(object):
|
|||
reserved_names.extend([
|
||||
'ds', 'y', 'cap', 'floor', 'y_scaled', 'cap_scaled'])
|
||||
if name in reserved_names:
|
||||
raise ValueError('Name "{}" is reserved.'.format(name))
|
||||
raise ValueError(f'Name "{name}" is reserved.')
|
||||
if (check_holidays and self.holidays is not None and
|
||||
name in self.holidays['holiday'].unique()):
|
||||
raise ValueError(
|
||||
'Name "{}" already used for a holiday.'.format(name))
|
||||
f'Name "{name}" already used for a holiday.')
|
||||
if (check_holidays and self.country_holidays is not None and
|
||||
name in get_holiday_names(self.country_holidays)):
|
||||
raise ValueError(
|
||||
'Name "{}" is a holiday name in {}.'.format(name, self.country_holidays))
|
||||
f'Name "{name}" is a holiday name in {self.country_holidays}.')
|
||||
if check_seasonalities and name in self.seasonalities:
|
||||
raise ValueError(
|
||||
'Name "{}" already used for a seasonality.'.format(name))
|
||||
f'Name "{name}" already used for a seasonality.')
|
||||
if check_regressors and name in self.extra_regressors:
|
||||
raise ValueError(
|
||||
'Name "{}" already used for an added regressor.'.format(name))
|
||||
'Name "{name}" already used for an added regressor.')
|
||||
|
||||
def setup_dataframe(self, df, initialize_scales=False):
|
||||
"""Prepare dataframe for fitting or predicting.
|
||||
|
|
@ -231,10 +231,9 @@ class Prophet(object):
|
|||
-------
|
||||
pd.DataFrame prepared for fitting or predicting.
|
||||
"""
|
||||
if 'y' in df:
|
||||
df['y'] = pd.to_numeric(df['y'])
|
||||
if np.isinf(df['y'].values).any():
|
||||
raise ValueError('Found infinity in column y.')
|
||||
df['y'] = pd.to_numeric(df['y'])
|
||||
if np.isinf(df['y'].values).any():
|
||||
raise ValueError('Found infinity in column y.')
|
||||
if df['ds'].dtype == np.int64:
|
||||
df['ds'] = df['ds'].astype(str)
|
||||
df['ds'] = pd.to_datetime(df['ds'])
|
||||
|
|
@ -248,18 +247,19 @@ class Prophet(object):
|
|||
for name in self.extra_regressors:
|
||||
if name not in df:
|
||||
raise ValueError(
|
||||
'Regressor "{}" missing from dataframe'.format(name))
|
||||
f'Regressor "{name}" missing from dataframe')
|
||||
df[name] = pd.to_numeric(df[name])
|
||||
if df[name].isnull().any():
|
||||
raise ValueError('Found NaN in column ' + name)
|
||||
raise ValueError(f'Found NaN in column {name}')
|
||||
for props in self.seasonalities.values():
|
||||
condition_name = props['condition_name']
|
||||
if condition_name is not None:
|
||||
if condition_name not in df:
|
||||
raise ValueError(
|
||||
'Condition "{}" missing from dataframe'.format(condition_name))
|
||||
f'Condition "{condition}" missing from dataframe')
|
||||
if not df[condition_name].isin([True, False]).all():
|
||||
raise ValueError('Found non-boolean in column ' + condition_name)
|
||||
raise ValueError(
|
||||
f'Found non-boolean in column {condition_name}')
|
||||
df[condition_name] = df[condition_name].astype('bool')
|
||||
|
||||
if df.index.name == 'ds':
|
||||
|
|
@ -271,14 +271,14 @@ class Prophet(object):
|
|||
|
||||
if self.logistic_floor:
|
||||
if 'floor' not in df:
|
||||
raise ValueError("Expected column 'floor'.")
|
||||
raise ValueError('Expected column "floor".')
|
||||
else:
|
||||
df['floor'] = 0
|
||||
if self.growth == 'logistic':
|
||||
if 'cap' not in df:
|
||||
raise ValueError(
|
||||
"Capacities must be supplied for logistic growth in "
|
||||
"column 'cap'"
|
||||
'Capacities must be supplied for logistic growth in '
|
||||
'column "cap"'
|
||||
)
|
||||
if (df['cap'] <= df['floor']).any():
|
||||
raise ValueError(
|
||||
|
|
@ -287,8 +287,7 @@ class Prophet(object):
|
|||
df['cap_scaled'] = (df['cap'] - df['floor']) / self.y_scale
|
||||
|
||||
df['t'] = (df['ds'] - self.start) / self.t_scale
|
||||
if 'y' in df:
|
||||
df['y_scaled'] = (df['y'] - df['floor']) / self.y_scale
|
||||
df['y_scaled'] = (df['y'] - df['floor']) / self.y_scale
|
||||
|
||||
for name, props in self.extra_regressors.items():
|
||||
df[name] = ((df[name] - props['mu']) / props['std'])
|
||||
|
|
@ -323,8 +322,7 @@ class Prophet(object):
|
|||
standardize = False
|
||||
if standardize == 'auto':
|
||||
if set(df[name].unique()) == set([1, 0]):
|
||||
# Don't standardize binary variables.
|
||||
standardize = False
|
||||
standardize = False # Don't standardize binary variables.
|
||||
else:
|
||||
standardize = True
|
||||
if standardize:
|
||||
|
|
@ -354,14 +352,14 @@ class Prophet(object):
|
|||
'Changepoints must fall within training data.')
|
||||
else:
|
||||
# Place potential changepoints evenly through first
|
||||
# changepoint_range proportion of the history
|
||||
# {changepoint_range} proportion of the history
|
||||
hist_size = int(np.floor(
|
||||
self.history.shape[0] * self.changepoint_range))
|
||||
if self.n_changepoints + 1 > hist_size:
|
||||
self.n_changepoints = hist_size - 1
|
||||
logger.info(
|
||||
'n_changepoints greater than number of observations.'
|
||||
'Using {}.'.format(self.n_changepoints)
|
||||
f'n_changepoints greater than number of observations. '
|
||||
'Using {self.n_changepoints}.'
|
||||
)
|
||||
if self.n_changepoints > 0:
|
||||
cp_indexes = (
|
||||
|
|
@ -454,10 +452,10 @@ class Prophet(object):
|
|||
country_holidays_df = make_holidays_df(
|
||||
year_list=year_list, country=self.country_holidays
|
||||
)
|
||||
all_holidays = pd.concat((all_holidays, country_holidays_df), sort=False)
|
||||
all_holidays = pd.concat((all_holidays, country_holidays_df),
|
||||
sort=False)
|
||||
all_holidays.reset_index(drop=True, inplace=True)
|
||||
# If the model has already been fit with a certain set of holidays,
|
||||
# make sure we are using those same ones.
|
||||
# Drop holidays that were not seen in data used to fit model
|
||||
if self.train_holiday_names is not None:
|
||||
# Remove holiday names didn't show up in fit
|
||||
index_to_drop = all_holidays.index[
|
||||
|
|
@ -469,10 +467,12 @@ class Prophet(object):
|
|||
# Add holiday names in fit but not in predict with ds as NA
|
||||
holidays_to_add = pd.DataFrame({
|
||||
'holiday': self.train_holiday_names[
|
||||
np.logical_not(self.train_holiday_names.isin(all_holidays.holiday))
|
||||
np.logical_not(self.train_holiday_names
|
||||
.isin(all_holidays.holiday))
|
||||
]
|
||||
})
|
||||
all_holidays = pd.concat((all_holidays, holidays_to_add), sort=False)
|
||||
all_holidays = pd.concat((all_holidays, holidays_to_add),
|
||||
sort=False)
|
||||
all_holidays.reset_index(drop=True, inplace=True)
|
||||
return all_holidays
|
||||
|
||||
|
|
@ -509,12 +509,10 @@ class Prophet(object):
|
|||
ps = float(row.get('prior_scale', self.holidays_prior_scale))
|
||||
if np.isnan(ps):
|
||||
ps = float(self.holidays_prior_scale)
|
||||
if (
|
||||
row.holiday in prior_scales and prior_scales[row.holiday] != ps
|
||||
):
|
||||
if row.holiday in prior_scales and prior_scales[row.holiday] != ps:
|
||||
raise ValueError(
|
||||
'Holiday {} does not have consistent prior scale '
|
||||
'specification.'.format(row.holiday))
|
||||
f'Holiday {row.holiday} does not have consistent prior '
|
||||
'scale specification.')
|
||||
if ps <= 0:
|
||||
raise ValueError('Prior scale must be > 0')
|
||||
prior_scales[row.holiday] = ps
|
||||
|
|
@ -525,7 +523,6 @@ class Prophet(object):
|
|||
loc = row_index.get_loc(occurrence)
|
||||
except KeyError:
|
||||
loc = None
|
||||
|
||||
key = '{}_delim_{}{}'.format(
|
||||
row.holiday,
|
||||
'+' if offset >= 0 else '-',
|
||||
|
|
@ -534,11 +531,11 @@ class Prophet(object):
|
|||
if loc is not None:
|
||||
expanded_holidays[key][loc] = 1.
|
||||
else:
|
||||
# Access key to generate value
|
||||
expanded_holidays[key]
|
||||
expanded_holidays[key] # Access key to generate value
|
||||
holiday_features = pd.DataFrame(expanded_holidays)
|
||||
# Make sure column order is consistent
|
||||
holiday_features = holiday_features[sorted(holiday_features.columns.tolist())]
|
||||
holiday_features = holiday_features[sorted(holiday_features.columns
|
||||
.tolist())]
|
||||
prior_scale_list = [
|
||||
prior_scales[h.split('_delim_')[0]]
|
||||
for h in holiday_features.columns
|
||||
|
|
@ -549,9 +546,8 @@ class Prophet(object):
|
|||
self.train_holiday_names = pd.Series(holiday_names)
|
||||
return holiday_features, prior_scale_list, holiday_names
|
||||
|
||||
def add_regressor(
|
||||
self, name, prior_scale=None, standardize='auto', mode=None
|
||||
):
|
||||
def add_regressor(self, name, prior_scale=None, standardize='auto',
|
||||
mode=None):
|
||||
"""Add an additional regressor to be used for fitting and predicting.
|
||||
|
||||
The dataframe passed to `fit` and `predict` will have a column with the
|
||||
|
|
@ -601,9 +597,8 @@ class Prophet(object):
|
|||
}
|
||||
return self
|
||||
|
||||
def add_seasonality(
|
||||
self, name, period, fourier_order, prior_scale=None, mode=None, condition_name=None
|
||||
):
|
||||
def add_seasonality(self, name, period, fourier_order, prior_scale=None,
|
||||
mode=None, condition_name=None):
|
||||
"""Add a seasonal component with specified period, number of Fourier
|
||||
components, and prior scale.
|
||||
|
||||
|
|
@ -621,9 +616,9 @@ class Prophet(object):
|
|||
Additive means the seasonality will be added to the trend,
|
||||
multiplicative means it will multiply the trend.
|
||||
|
||||
If condition_name is provided, the dataframe passed to `fit` and `predict`
|
||||
should have a column with the specified condition_name containing booleans
|
||||
which decides when to apply seasonality.
|
||||
If condition_name is provided, the dataframe passed to `fit` and
|
||||
`predict` should have a column with the specified condition_name
|
||||
containing booleans which decides when to apply seasonality.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
|
|
@ -640,7 +635,7 @@ class Prophet(object):
|
|||
"""
|
||||
if self.history is not None:
|
||||
raise Exception(
|
||||
"Seasonality must be added prior to model fitting.")
|
||||
'Seasonality must be added prior to model fitting.')
|
||||
if name not in ['daily', 'weekly', 'yearly']:
|
||||
# Allow overwriting built-in seasonalities
|
||||
self.validate_column_name(name, check_seasonalities=False)
|
||||
|
|
@ -655,7 +650,7 @@ class Prophet(object):
|
|||
if mode is None:
|
||||
mode = self.seasonality_mode
|
||||
if mode not in ['additive', 'multiplicative']:
|
||||
raise ValueError("mode must be 'additive' or 'multiplicative'")
|
||||
raise ValueError('mode must be "additive" or "multiplicative"')
|
||||
if condition_name is not None:
|
||||
self.validate_column_name(condition_name)
|
||||
self.seasonalities[name] = {
|
||||
|
|
@ -698,9 +693,8 @@ class Prophet(object):
|
|||
# Set the holidays.
|
||||
if self.country_holidays is not None:
|
||||
logger.warning(
|
||||
'Changing country holidays from {} to {}'.format(
|
||||
self.country_holidays, country_name
|
||||
)
|
||||
f'Changing country holidays from {self.country_holidays} to '
|
||||
'{country_name}.'
|
||||
)
|
||||
self.country_holidays = country_name
|
||||
return self
|
||||
|
|
@ -828,10 +822,8 @@ class Prophet(object):
|
|||
# Remove the placeholder
|
||||
component_cols.drop('zeros', axis=1, inplace=True, errors='ignore')
|
||||
# Validation
|
||||
if (
|
||||
max(component_cols['additive_terms']
|
||||
+ component_cols['multiplicative_terms']) > 1
|
||||
):
|
||||
if (max(component_cols['additive_terms']
|
||||
+ component_cols['multiplicative_terms']) > 1):
|
||||
raise Exception('A bug occurred in seasonal components.')
|
||||
# Compare to the training, if set.
|
||||
if self.train_component_cols is not None:
|
||||
|
|
@ -879,14 +871,13 @@ class Prophet(object):
|
|||
fourier_order = 0
|
||||
if name in self.seasonalities:
|
||||
logger.info(
|
||||
'Found custom seasonality named "{name}", '
|
||||
'disabling built-in {name} seasonality.'.format(name=name)
|
||||
f'Found custom seasonality named "{name}", '
|
||||
'disabling built-in {name} seasonality.'
|
||||
)
|
||||
elif auto_disable:
|
||||
logger.info(
|
||||
'Disabling {name} seasonality. Run prophet with '
|
||||
'{name}_seasonality=True to override this.'.format(
|
||||
name=name)
|
||||
f'Disabling {name} seasonality. Run prophet with '
|
||||
'{name}_seasonality=True to override this.'
|
||||
)
|
||||
else:
|
||||
fourier_order = default_order
|
||||
|
|
@ -1049,8 +1040,8 @@ class Prophet(object):
|
|||
'Instantiate a new object.')
|
||||
if ('ds' not in df) or ('y' not in df):
|
||||
raise ValueError(
|
||||
"Dataframe must have columns 'ds' and 'y' with the dates and "
|
||||
"values respectively."
|
||||
'Dataframe must have columns "ds" and "y" with the dates and '
|
||||
'values respectively.'
|
||||
)
|
||||
history = df[df['y'].notnull()].copy()
|
||||
if history.shape[0] < 2:
|
||||
|
|
@ -1100,10 +1091,8 @@ class Prophet(object):
|
|||
'sigma_obs': 1,
|
||||
}
|
||||
|
||||
if (
|
||||
(history['y'].min() == history['y'].max())
|
||||
and self.growth == 'linear'
|
||||
):
|
||||
if ((history['y'].min() == history['y'].max())
|
||||
and (self.growth == 'linear')):
|
||||
# Nothing to fit.
|
||||
self.params = stan_init()
|
||||
self.params['sigma_obs'] = 1e-9
|
||||
|
|
@ -1120,7 +1109,8 @@ class Prophet(object):
|
|||
for par in self.stan_fit.model_pars:
|
||||
self.params[par] = self.stan_fit[par]
|
||||
# Shape vector parameters
|
||||
if par in ['delta', 'beta'] and len(self.params[par].shape) < 2:
|
||||
if (par in ['delta', 'beta']
|
||||
and len(self.params[par].shape) < 2):
|
||||
self.params[par] = self.params[par].reshape((-1, 1))
|
||||
else:
|
||||
args = dict(
|
||||
|
|
@ -1133,9 +1123,9 @@ class Prophet(object):
|
|||
try:
|
||||
self.stan_fit = model.optimizing(**args)
|
||||
except RuntimeError:
|
||||
# Fall back on Newton
|
||||
logger.warning(
|
||||
'Optimization terminated abnormally. Falling back to Newton.'
|
||||
'Optimization terminated abnormally. '
|
||||
'Falling back to Newton.'
|
||||
)
|
||||
args['algorithm'] = 'Newton'
|
||||
self.stan_fit = model.optimizing(**args)
|
||||
|
|
@ -1146,8 +1136,10 @@ class Prophet(object):
|
|||
# If no changepoints were requested, replace delta with 0s
|
||||
if len(self.changepoints) == 0:
|
||||
# Fold delta into the base rate k
|
||||
self.params['k'] = self.params['k'] + self.params['delta'].reshape(-1)
|
||||
self.params['delta'] = np.zeros(self.params['delta'].shape).reshape((-1, 1))
|
||||
self.params['k'] = (self.params['k']
|
||||
+ self.params['delta'].reshape(-1))
|
||||
self.params['delta'] = (np.zeros(self.params['delta'].shape)
|
||||
.reshape((-1, 1)))
|
||||
|
||||
return self
|
||||
|
||||
|
|
@ -1165,7 +1157,7 @@ class Prophet(object):
|
|||
A pd.DataFrame with the forecast components.
|
||||
"""
|
||||
if self.history is None:
|
||||
raise Exception('Model must be fit before predictions can be made.')
|
||||
raise Exception('Model has not been fit.')
|
||||
|
||||
if df is None:
|
||||
df = self.history.copy()
|
||||
|
|
@ -1415,7 +1407,8 @@ class Prophet(object):
|
|||
trend = self.sample_predictive_trend(df, iteration)
|
||||
|
||||
beta = self.params['beta'][iteration]
|
||||
Xb_a = np.matmul(seasonal_features.values, beta * s_a.values) * self.y_scale
|
||||
Xb_a = np.matmul(seasonal_features.values,
|
||||
beta * s_a.values) * self.y_scale
|
||||
Xb_m = np.matmul(seasonal_features.values, beta * s_m.values)
|
||||
|
||||
sigma = self.params['sigma_obs'][iteration]
|
||||
|
|
@ -1493,7 +1486,7 @@ class Prophet(object):
|
|||
requested number of periods.
|
||||
"""
|
||||
if self.history_dates is None:
|
||||
raise Exception('Model must be fit before this can be used.')
|
||||
raise Exception('Model as not been fit.')
|
||||
last_date = self.history_dates.max()
|
||||
dates = pd.date_range(
|
||||
start=last_date,
|
||||
|
|
@ -1507,8 +1500,8 @@ class Prophet(object):
|
|||
|
||||
return pd.DataFrame({'ds': dates})
|
||||
|
||||
def plot(self, fcst, ax=None, uncertainty=True, plot_cap=True, xlabel='ds',
|
||||
ylabel='y', figsize=(10, 6)):
|
||||
def plot(self, fcst, ax=None, uncertainty=True, plot_cap=True,
|
||||
xlabel='ds', ylabel='y', figsize=(10, 6)):
|
||||
"""Plot the Prophet forecast.
|
||||
|
||||
Parameters
|
||||
|
|
|
|||
|
|
@ -259,8 +259,10 @@ class TestDiagnostics(TestCase):
|
|||
self.assertTrue((m1.holidays == m2.holidays).values.all())
|
||||
self.assertEqual(m1.country_holidays, m2.country_holidays)
|
||||
self.assertEqual(m1.seasonality_mode, m2.seasonality_mode)
|
||||
self.assertEqual(m1.seasonality_prior_scale, m2.seasonality_prior_scale)
|
||||
self.assertEqual(m1.changepoint_prior_scale, m2.changepoint_prior_scale)
|
||||
self.assertEqual(m1.seasonality_prior_scale,
|
||||
m2.seasonality_prior_scale)
|
||||
self.assertEqual(m1.changepoint_prior_scale,
|
||||
m2.changepoint_prior_scale)
|
||||
self.assertEqual(m1.holidays_prior_scale, m2.holidays_prior_scale)
|
||||
self.assertEqual(m1.mcmc_samples, m2.mcmc_samples)
|
||||
self.assertEqual(m1.interval_width, m2.interval_width)
|
||||
|
|
|
|||
|
|
@ -42,7 +42,8 @@ class TestProphet(TestCase):
|
|||
train = DATA.head(N // 2)
|
||||
future = DATA.tail(N // 2)
|
||||
|
||||
forecaster = Prophet(weekly_seasonality=False, yearly_seasonality=False)
|
||||
forecaster = Prophet(weekly_seasonality=False,
|
||||
yearly_seasonality=False)
|
||||
forecaster.fit(train)
|
||||
forecaster.predict(future)
|
||||
|
||||
|
|
@ -102,8 +103,10 @@ class TestProphet(TestCase):
|
|||
m = Prophet(uncertainty_samples=uncertainty)
|
||||
m.fit(train)
|
||||
fcst = m.predict(future)
|
||||
expected_cols = ['ds', 'trend', 'additive_terms', 'multiplicative_terms', 'weekly', 'yhat']
|
||||
self.assertTrue(all(col in expected_cols for col in fcst.columns.tolist()))
|
||||
expected_cols = ['ds', 'trend', 'additive_terms',
|
||||
'multiplicative_terms', 'weekly', 'yhat']
|
||||
self.assertTrue(all(col in expected_cols
|
||||
for col in fcst.columns.tolist()))
|
||||
|
||||
def test_setup_dataframe(self):
|
||||
m = Prophet()
|
||||
|
|
@ -223,7 +226,7 @@ class TestProphet(TestCase):
|
|||
mat = Prophet.fourier_series(DATA['ds'], 7, 3)
|
||||
# These are from the R forecast package directly.
|
||||
true_values = np.array([
|
||||
0.7818315, 0.6234898, 0.9749279, -0.2225209, 0.4338837, -0.9009689,
|
||||
0.7818315, 0.6234898, 0.9749279, -0.2225209, 0.4338837, -0.9009689
|
||||
])
|
||||
self.assertAlmostEqual(np.sum((mat[0] - true_values)**2), 0.0)
|
||||
|
||||
|
|
@ -231,7 +234,7 @@ class TestProphet(TestCase):
|
|||
mat = Prophet.fourier_series(DATA['ds'], 365.25, 3)
|
||||
# These are from the R forecast package directly.
|
||||
true_values = np.array([
|
||||
0.7006152, -0.7135393, -0.9998330, 0.01827656, 0.7262249, 0.6874572,
|
||||
0.7006152, -0.7135393, -0.9998330, 0.01827656, 0.7262249, 0.6874572
|
||||
])
|
||||
self.assertAlmostEqual(np.sum((mat[0] - true_values)**2), 0.0)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue