def plot_lagged_weights(weights, comp_name, focus=None, ax=None, figsize=(10, 6)): """Make a barplot of the importance of lagged inputs. Args: weights (np.array): model weights as matrix or vector comp_name (str): name of lagged inputs focus (int): if provided, show weights for this forecast None (default) sum over all forecasts and plot as relative percentage ax (matplotlib axis): matplotlib Axes to plot on. One will be created if this is not provided. figsize (tuple): width, height in inches. Ignored if ax is not None. default: (10, 6) Returns: a list of matplotlib artists """ artists = [] if not ax: fig = plt.figure(facecolor="w", figsize=figsize) ax = fig.add_subplot(111) n_lags = weights.shape[1] lags_range = list(range(1, 1 + n_lags))[::-1] if focus is None: weights = np.sum(np.abs(weights), axis=0) weights = weights / np.sum(weights) artists += ax.bar(lags_range, weights, width=1.00, color="#0072B2") else: if len(weights.shape) == 2: weights = weights[focus - 1, :] artists += ax.bar(lags_range, weights, width=0.80, color="#0072B2") ax.grid(True, which="major", c="gray", ls="-", lw=1, alpha=0.2) ax.set_xlabel("{} lag number".format(comp_name)) if focus is None: ax.set_ylabel("{} relevance".format(comp_name)) ax = set_y_as_percent(ax) else: ax.set_ylabel("{} weight ({})-ahead".format(comp_name, focus)) return artists
def plot_parameters(m, forecast_in_focus=None, weekly_start=0, yearly_start=0, figsize=None): """Plot the parameters that the model is composed of, visually. Args: m (NeuralProphet): fitted model. forecast_in_focus (int): n-th step ahead forecast AR-coefficients to plot weekly_start (int): specifying the start day of the weekly seasonality plot. 0 (default) starts the week on Sunday. 1 shifts by 1 day to Monday, and so on. yearly_start (int): specifying the start day of the yearly seasonality plot. 0 (default) starts the year on Jan 1. 1 shifts by 1 day to Jan 2, and so on. figsize (tuple): width, height in inches. None (default): automatic (10, 3 * npanel) Returns: A matplotlib figure. """ # Identify components to be plotted # as dict: {plot_name, } components = [{"plot_name": "Trend"}] if m.config_trend.n_changepoints > 0: components.append({"plot_name": "Trend Rate Change"}) # Plot seasonalities, if present if m.season_config is not None: for name in m.season_config.periods: components.append({"plot_name": "seasonality", "comp_name": name}) if m.n_lags > 0: components.append({ "plot_name": "lagged weights", "comp_name": "AR", "weights": m.model.ar_weights.detach().numpy(), "focus": forecast_in_focus, }) # all scalar regressors will be plotted together # collected as tuples (name, weights) # Add Regressors additive_future_regressors = [] multiplicative_future_regressors = [] if m.regressors_config is not None: for regressor, configs in m.regressors_config.items(): mode = configs["mode"] regressor_param = m.model.get_reg_weights(regressor) if mode == "additive": additive_future_regressors.append( (regressor, regressor_param.detach().numpy())) else: multiplicative_future_regressors.append( (regressor, regressor_param.detach().numpy())) additive_events = [] multiplicative_events = [] # Add Events # add the country holidays if m.country_holidays_config is not None: for country_holiday in m.country_holidays_config["holiday_names"]: event_params = m.model.get_event_weights(country_holiday) weight_list = [(key, param.detach().numpy()) for key, param in event_params.items()] mode = m.country_holidays_config["mode"] if mode == "additive": additive_events = additive_events + weight_list else: multiplicative_events = multiplicative_events + weight_list # add the user specified events if m.events_config is not None: for event, configs in m.events_config.items(): event_params = m.model.get_event_weights(event) weight_list = [(key, param.detach().numpy()) for key, param in event_params.items()] mode = configs["mode"] if mode == "additive": additive_events = additive_events + weight_list else: multiplicative_events = multiplicative_events + weight_list # Add Covariates lagged_scalar_regressors = [] if m.covar_config is not None: for name in m.covar_config.keys(): if m.covar_config[name].as_scalar: lagged_scalar_regressors.append( (name, m.model.get_covar_weights(name).detach().numpy())) else: components.append({ "plot_name": "lagged weights", "comp_name": 'Lagged Regressor "{}"'.format(name), "weights": m.model.get_covar_weights(name).detach().numpy(), "focus": forecast_in_focus, }) if len(additive_future_regressors) > 0: components.append({"plot_name": "Additive future regressor"}) if len(multiplicative_future_regressors) > 0: components.append({"plot_name": "Multiplicative future regressor"}) if len(lagged_scalar_regressors) > 0: components.append({"plot_name": "Lagged scalar regressor"}) if len(additive_events) > 0: additive_events = [(key, weight * m.data_params["y"].scale) for (key, weight) in additive_events] components.append({"plot_name": "Additive event"}) if len(multiplicative_events) > 0: components.append({"plot_name": "Multiplicative event"}) npanel = len(components) figsize = figsize if figsize else (10, 3 * npanel) fig, axes = plt.subplots(npanel, 1, facecolor="w", figsize=figsize) if npanel == 1: axes = [axes] multiplicative_axes = [] for ax, comp in zip(axes, components): plot_name = comp["plot_name"].lower() if plot_name.startswith("trend"): if "change" in plot_name: plot_trend_change(m=m, ax=ax, plot_name=comp["plot_name"]) else: plot_trend(m=m, ax=ax, plot_name=comp["plot_name"]) elif plot_name.startswith("seasonality"): name = comp["comp_name"] if m.season_config.mode == "multiplicative": multiplicative_axes.append(ax) if name.lower( ) == "weekly" or m.season_config.periods[name].period == 7: plot_weekly(m=m, ax=ax, weekly_start=weekly_start, comp_name=name) elif name.lower( ) == "yearly" or m.season_config.periods[name].period == 365.25: plot_yearly(m=m, ax=ax, yearly_start=yearly_start, comp_name=name) elif name.lower( ) == "daily" or m.season_config.periods[name].period == 1: plot_daily(m=m, ax=ax, comp_name=name) else: plot_custom_season(m=m, ax=ax, comp_name=name) elif plot_name == "lagged weights": plot_lagged_weights(weights=comp["weights"], comp_name=comp["comp_name"], focus=comp["focus"], ax=ax) else: if plot_name == "additive future regressor": weights = additive_future_regressors elif plot_name == "multiplicative future regressor": multiplicative_axes.append(ax) weights = multiplicative_future_regressors elif plot_name == "lagged scalar regressor": weights = lagged_scalar_regressors elif plot_name == "additive event": weights = additive_events elif plot_name == "multiplicative event": multiplicative_axes.append(ax) weights = multiplicative_events plot_scalar_weights(weights=weights, plot_name=comp["plot_name"], focus=forecast_in_focus, ax=ax) fig.tight_layout() # Reset multiplicative axes labels after tight_layout adjustment for ax in multiplicative_axes: ax = set_y_as_percent(ax) return fig
def plot_multiforecast_component( fcst, comp_name, plot_name=None, ax=None, figsize=(10, 6), multiplicative=False, bar=False, focus=1, num_overplot=None, ): """Plot a particular component of the forecast. Args: fcst (pd.DataFrame): output of m.predict. comp_name (str): Name of the component to plot. plot_name (str): Name of the plot Title. ax (matplotlib axis): matplotlib Axes to plot on. figsize (tuple): width, height in inches. Ignored if ax is not None. default: (10, 6) multiplicative (bool): set y axis as percentage bar (bool): make barplot focus (int): forecast number to portray in detail. num_overplot (int): overplot all forecasts up to num None (default): only plot focus Returns: a list of matplotlib artists """ artists = [] if not ax: fig = plt.figure(facecolor="w", figsize=figsize) ax = fig.add_subplot(111) fcst_t = fcst["ds"].dt.to_pydatetime() col_names = [ col_name for col_name in fcst.columns if col_name.startswith(comp_name) ] if num_overplot is not None: assert num_overplot <= len(col_names) for i in list(range(num_overplot))[::-1]: y = fcst["{}{}".format(comp_name, i + 1)] notnull = y.notnull() y = y.values alpha_min = 0.2 alpha_softness = 1.2 alpha = alpha_min + alpha_softness * (1.0 - alpha_min) / ( i + 1.0 * alpha_softness) if "residual" not in comp_name: pass # fcst_t=fcst_t[notnull] # y = y[notnull] else: y[-1] = 0 if bar: artists += ax.bar(fcst_t, y, width=1.00, color="#0072B2", alpha=alpha) else: artists += ax.plot(fcst_t, y, ls="-", color="#0072B2", alpha=alpha) if num_overplot is None or focus > 1: y = fcst["{}{}".format(comp_name, focus)] notnull = y.notnull() y = y.values if "residual" not in comp_name: fcst_t = fcst_t[notnull] y = y[notnull] else: y[-1] = 0 if bar: artists += ax.bar(fcst_t, y, width=1.00, color="b") else: artists += ax.plot(fcst_t, y, ls="-", color="b") # Specify formatting to workaround matplotlib issue #12925 locator = AutoDateLocator(interval_multiples=False) formatter = AutoDateFormatter(locator) ax.xaxis.set_major_locator(locator) ax.xaxis.set_major_formatter(formatter) ax.grid(True, which="major", color="gray", ls="-", lw=1, alpha=0.2) ax.set_xlabel("ds") if plot_name is None: plot_name = comp_name ax.set_ylabel(plot_name) if multiplicative: ax = set_y_as_percent(ax) return artists
def plot_components(m, fcst, forecast_in_focus=None, one_period_per_season=True, residuals=False, figsize=None): """Plot the NeuralProphet forecast components. Args: m (NeuralProphet): fitted model. fcst (pd.DataFrame): output of m.predict. forecast_in_focus (int): n-th step ahead forecast AR-coefficients to plot one_period_per_season (bool): plot one period per season instead of the true seasonal components of the forecast. figsize (tuple): width, height in inches. None (default): automatic (10, 3 * npanel) Returns: A matplotlib figure. """ log.debug("Plotting forecast components".format(fcst.head().to_string())) fcst = fcst.fillna(value=np.nan) # Identify components to be plotted # as dict, minimum: {plot_name, comp_name} components = [] # Plot trend components.append({"plot_name": "Trend", "comp_name": "trend"}) # Plot seasonalities, if present if m.model.config_season is not None: for name in m.model.config_season.periods: components.append({ "plot_name": "{} seasonality".format(name), "comp_name": name, }) # AR if m.model.n_lags > 0: if forecast_in_focus is None: components.append({ "plot_name": "Auto-Regression", "comp_name": "ar", "num_overplot": m.n_forecasts, "bar": True, }) else: components.append({ "plot_name": "AR ({})-ahead".format(forecast_in_focus), "comp_name": "ar{}".format(forecast_in_focus), }) # 'add_x': True}) # Add Covariates if m.model.config_covar is not None: for name in m.model.config_covar.keys(): if forecast_in_focus is None: components.append({ "plot_name": 'Lagged Regressor "{}"'.format(name), "comp_name": "lagged_regressor_{}".format(name), "num_overplot": m.n_forecasts, "bar": True, }) else: components.append({ "plot_name": 'Lagged Regressor "{}" ({})-ahead'.format( name, forecast_in_focus), "comp_name": "lagged_regressor_{}{}".format(name, forecast_in_focus), }) # 'add_x': True}) # Add Events if "events_additive" in fcst.columns: components.append({ "plot_name": "Additive Events", "comp_name": "events_additive", }) if "events_multiplicative" in fcst.columns: components.append({ "plot_name": "Multiplicative Events", "comp_name": "events_multiplicative", "multiplicative": True, }) # Add Regressors if "future_regressors_additive" in fcst.columns: components.append({ "plot_name": "Additive Future Regressors", "comp_name": "future_regressors_additive", }) if "future_regressors_multiplicative" in fcst.columns: components.append({ "plot_name": "Multiplicative Future Regressors", "comp_name": "future_regressors_multiplicative", "multiplicative": True, }) if residuals: if forecast_in_focus is None and m.n_forecasts > 1: if fcst["residual1"].count() > 0: components.append({ "plot_name": "Residuals", "comp_name": "residual", "num_overplot": m.n_forecasts, "bar": True, }) else: ahead = 1 if forecast_in_focus is None else forecast_in_focus if fcst["residual{}".format(ahead)].count() > 0: components.append({ "plot_name": "Residuals ({})-ahead".format(ahead), "comp_name": "residual{}".format(ahead), "bar": True, }) npanel = len(components) figsize = figsize if figsize else (10, 3 * npanel) fig, axes = plt.subplots(npanel, 1, facecolor="w", figsize=figsize) if npanel == 1: axes = [axes] multiplicative_axes = [] for ax, comp in zip(axes, components): name = comp["plot_name"].lower() if (name in ["trend"] or ("residuals" in name and "ahead" in name) or ("ar" in name and "ahead" in name) or ("lagged_regressor" in name and "ahead" in name)): plot_forecast_component(fcst=fcst, ax=ax, **comp) elif "event" in name or "future regressor" in name: if "multiplicative" in comp.keys() and comp["multiplicative"]: multiplicative_axes.append(ax) plot_forecast_component(fcst=fcst, ax=ax, **comp) elif "season" in name: if m.season_config.mode == "multiplicative": multiplicative_axes.append(ax) if one_period_per_season: comp_name = comp["comp_name"] if comp_name.lower() == "weekly" or m.season_config.periods[ comp_name].period == 7: plot_weekly(m=m, ax=ax, comp_name=comp_name) elif comp_name.lower() == "yearly" or m.season_config.periods[ comp_name].period == 365.25: plot_yearly(m=m, ax=ax, comp_name=comp_name) elif comp_name.lower( ) == "daily" or m.season_config.periods[comp_name].period == 1: plot_daily(m=m, ax=ax, comp_name=comp_name) else: plot_custom_season(m=m, ax=ax, comp_name=comp_name) else: comp_name = "season_{}".format(comp["comp_name"]) plot_forecast_component(fcst=fcst, ax=ax, comp_name=comp_name, plot_name=comp["plot_name"]) elif "auto-regression" in name or "lagged regressor" in name or "residuals" in name: plot_multiforecast_component(fcst=fcst, ax=ax, **comp) fig.tight_layout() # Reset multiplicative axes labels after tight_layout adjustment for ax in multiplicative_axes: ax = set_y_as_percent(ax) return fig
def plot_forecast_component( fcst, comp_name, plot_name=None, ax=None, figsize=(10, 6), multiplicative=False, bar=False, rolling=None, add_x=False, ): """Plot a particular component of the forecast. Args: fcst (pd.DataFrame): output of m.predict. comp_name (str): Name of the component to plot. plot_name (str): Name of the plot Title. ax (matplotlib axis): matplotlib Axes to plot on. figsize (tuple): width, height in inches. Ignored if ax is not None. default: (10, 6) multiplicative (bool): set y axis as percentage bar (bool): make barplot rolling (int): rolling average underplot add_x (bool): add x symbols to plotted points Returns: a list of matplotlib artists """ fcst = fcst.fillna(value=np.nan) artists = [] if not ax: fig = plt.figure(facecolor="w", figsize=figsize) ax = fig.add_subplot(111) fcst_t = fcst["ds"].dt.to_pydatetime() if rolling is not None: rolling_avg = fcst[comp_name].rolling(rolling, min_periods=1, center=True).mean() if bar: artists += ax.bar(fcst_t, rolling_avg, width=1.00, color="#0072B2", alpha=0.5) else: artists += ax.plot(fcst_t, rolling_avg, ls="-", color="#0072B2", alpha=0.5) if add_x: artists += ax.plot(fcst_t, fcst[comp_name], "bx") y = fcst[comp_name].values if "residual" in comp_name: y[-1] = 0 if bar: artists += ax.bar(fcst_t, y, width=1.00, color="#0072B2") else: artists += ax.plot(fcst_t, y, ls="-", c="#0072B2") if add_x or sum(fcst[comp_name].notna()) == 1: artists += ax.plot(fcst_t, y, "bx") # Specify formatting to workaround matplotlib issue #12925 locator = AutoDateLocator(interval_multiples=False) formatter = AutoDateFormatter(locator) ax.xaxis.set_major_locator(locator) ax.xaxis.set_major_formatter(formatter) ax.grid(True, which="major", c="gray", ls="-", lw=1, alpha=0.2) ax.set_xlabel("ds") if plot_name is None: plot_name = comp_name ax.set_ylabel(plot_name) if multiplicative: ax = set_y_as_percent(ax) return artists