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
예제 #3
0
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
예제 #4
0
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
예제 #5
0
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