prophet.plot
1# Copyright (c) Facebook, Inc. and its affiliates. 2 3# This source code is licensed under the MIT license found in the 4# LICENSE file in the root directory of this source tree. 5 6from __future__ import annotations 7 8import logging 9from typing import TYPE_CHECKING, cast 10 11import numpy as np 12import pandas as pd 13 14# TODO: separate performance_metrics into a different module. there is an implicit circular import between forecaster.py and diagnostics.py 15from prophet.diagnostics import performance_metrics 16 17if TYPE_CHECKING: 18 from typing import Literal, Sequence, TypeVar, type_check_only 19 from typing_extensions import TypedDict 20 21 from prophet.forecaster import Prophet 22 23 import matplotlib.pyplot as plt 24 import plotly.graph_objs as go 25 26 _AxT = TypeVar('_AxT', bound=plt.Axes) 27 28 @type_check_only 29 class _PlotlyProps(TypedDict): 30 traces: list[go.Scatter] 31 xaxis: go.layout.XAxis 32 yaxis: go.layout.YAxis 33 34 35logger: logging.Logger = logging.getLogger('prophet.plot') 36 37 38try: 39 from matplotlib import pyplot as plt 40 from matplotlib.dates import ( 41 MonthLocator, 42 num2date, 43 AutoDateLocator, 44 AutoDateFormatter, 45 ) 46 from matplotlib.ticker import FuncFormatter 47 48 from pandas.plotting import deregister_matplotlib_converters 49 deregister_matplotlib_converters() 50except ImportError: 51 logger.error('Importing matplotlib failed. Plotting will not work.') 52 53try: 54 import plotly.graph_objs as go 55 from plotly.subplots import make_subplots 56except ImportError: 57 logger.error('Importing plotly failed. Interactive plots will not work.') 58 59 60def plot( 61 m: Prophet, 62 fcst: pd.DataFrame, 63 ax: plt.Axes | None = None, 64 uncertainty: bool = True, 65 plot_cap: bool = True, 66 xlabel: str = "ds", 67 ylabel: str = "y", 68 figsize: tuple[int, int] = (10, 6), 69 include_legend: bool = False, 70) -> plt.Figure: 71 """Plot the Prophet forecast. 72 73 Parameters 74 ---------- 75 m: Prophet model. 76 fcst: pd.DataFrame output of m.predict. 77 ax: Optional matplotlib axes on which to plot. 78 uncertainty: Optional boolean to plot uncertainty intervals, which will 79 only be done if m.uncertainty_samples > 0. 80 plot_cap: Optional boolean indicating if the capacity should be shown 81 in the figure, if available. 82 xlabel: Optional label name on X-axis 83 ylabel: Optional label name on Y-axis 84 figsize: Optional tuple width, height in inches. 85 include_legend: Optional boolean to add legend to the plot. 86 87 Returns 88 ------- 89 A matplotlib figure. 90 """ 91 user_provided_ax = False if ax is None else True 92 if ax is None: 93 fig = plt.figure(facecolor='w', figsize=figsize) 94 ax = fig.add_subplot(111) 95 else: 96 fig = cast('plt.Figure', ax.get_figure()) 97 fcst_t = fcst['ds'] 98 history = cast('pd.DataFrame', m.history) 99 ax.plot(history['ds'], history['y'], 'k.', label='Observed data points') 100 ax.plot(fcst_t, fcst['yhat'], ls='-', c='#0072B2', label='Forecast') 101 if 'cap' in fcst and plot_cap: 102 ax.plot(fcst_t, fcst['cap'], ls='--', c='k', label='Maximum capacity') 103 if m.logistic_floor and 'floor' in fcst and plot_cap: 104 ax.plot(fcst_t, fcst['floor'], ls='--', c='k', label='Minimum capacity') 105 if uncertainty and m.uncertainty_samples: 106 ax.fill_between(fcst_t, fcst['yhat_lower'], fcst['yhat_upper'], 107 color='#0072B2', alpha=0.2, label='Uncertainty interval') 108 # Specify formatting to workaround matplotlib issue #12925 109 locator = AutoDateLocator(interval_multiples=False) 110 formatter = AutoDateFormatter(locator) 111 ax.xaxis.set_major_locator(locator) 112 ax.xaxis.set_major_formatter(formatter) 113 ax.grid(True, which='major', c='gray', ls='-', lw=1, alpha=0.2) 114 ax.set_xlabel(xlabel) 115 ax.set_ylabel(ylabel) 116 if include_legend: 117 ax.legend() 118 if not user_provided_ax: 119 try: 120 if fig.get_layout_engine() is None: 121 fig.tight_layout() 122 except AttributeError: 123 fig.tight_layout() 124 return fig 125 126 127def plot_components( 128 m: Prophet, 129 fcst: pd.DataFrame, 130 uncertainty: bool = True, 131 plot_cap: bool = True, 132 weekly_start: int = 0, 133 yearly_start: int = 0, 134 figsize: tuple[int, int] | None = None, 135) -> plt.Figure: 136 """Plot the Prophet forecast components. 137 138 Will plot whichever are available of: trend, holidays, weekly 139 seasonality, yearly seasonality, and additive and multiplicative extra 140 regressors. 141 142 Parameters 143 ---------- 144 m: Prophet model. 145 fcst: pd.DataFrame output of m.predict. 146 uncertainty: Optional boolean to plot uncertainty intervals, which will 147 only be done if m.uncertainty_samples > 0. 148 plot_cap: Optional boolean indicating if the capacity should be shown 149 in the figure, if available. 150 weekly_start: Optional int specifying the start day of the weekly 151 seasonality plot. 0 (default) starts the week on Sunday. 1 shifts 152 by 1 day to Monday, and so on. 153 yearly_start: Optional int specifying the start day of the yearly 154 seasonality plot. 0 (default) starts the year on Jan 1. 1 shifts 155 by 1 day to Jan 2, and so on. 156 figsize: Optional tuple width, height in inches. 157 158 Returns 159 ------- 160 A matplotlib figure. 161 """ 162 # Identify components to be plotted 163 components = ['trend'] 164 if m.train_holiday_names is not None and 'holidays' in fcst: 165 components.append('holidays') 166 # Plot weekly seasonality, if present 167 if 'weekly' in m.seasonalities and 'weekly' in fcst: 168 components.append('weekly') 169 # Yearly if present 170 if 'yearly' in m.seasonalities and 'yearly' in fcst: 171 components.append('yearly') 172 # Other seasonalities 173 components.extend([ 174 name for name in sorted(m.seasonalities) 175 if name in fcst and name not in ['weekly', 'yearly'] 176 ]) 177 regressors = {'additive': False, 'multiplicative': False} 178 for name, props in m.extra_regressors.items(): 179 regressors[props['mode']] = True 180 for mode in ['additive', 'multiplicative']: 181 if regressors[mode] and 'extra_regressors_{}'.format(mode) in fcst: 182 components.append('extra_regressors_{}'.format(mode)) 183 npanel = len(components) 184 185 figsize = figsize if figsize else (9, 3 * npanel) 186 fig, axes = plt.subplots(npanel, 1, facecolor='w', figsize=figsize) 187 188 if npanel == 1: 189 axes = [axes] 190 191 multiplicative_axes = [] 192 193 dt = cast('pd.DataFrame', m.history)['ds'].diff() 194 min_dt = dt.iloc[cast('np.ndarray', dt.values).nonzero()[0]].min() 195 196 for ax, plot_name in zip(axes, components): 197 if plot_name == 'trend': 198 plot_forecast_component( 199 m=m, fcst=fcst, name='trend', ax=ax, uncertainty=uncertainty, 200 plot_cap=plot_cap, 201 ) 202 elif plot_name in m.seasonalities: 203 if ( 204 (plot_name == 'weekly' or m.seasonalities[plot_name]['period'] == 7) 205 and (min_dt == pd.Timedelta(days=1)) 206 ): 207 plot_weekly( 208 m=m, name=plot_name, ax=ax, uncertainty=uncertainty, weekly_start=weekly_start 209 ) 210 elif plot_name == 'yearly' or m.seasonalities[plot_name]['period'] == 365.25: 211 plot_yearly( 212 m=m, name=plot_name, ax=ax, uncertainty=uncertainty, yearly_start=yearly_start 213 ) 214 else: 215 plot_seasonality( 216 m=m, name=plot_name, ax=ax, uncertainty=uncertainty, 217 ) 218 elif plot_name in [ 219 'holidays', 220 'extra_regressors_additive', 221 'extra_regressors_multiplicative', 222 ]: 223 plot_forecast_component( 224 m=m, fcst=fcst, name=plot_name, ax=ax, uncertainty=uncertainty, 225 plot_cap=False, 226 ) 227 assert m.component_modes is not None 228 if plot_name in m.component_modes['multiplicative']: 229 multiplicative_axes.append(ax) 230 231 try: 232 if fig.get_layout_engine() is None: 233 fig.tight_layout() 234 except AttributeError: 235 fig.tight_layout() 236 # Reset multiplicative axes labels after tight_layout adjustment 237 for ax in multiplicative_axes: 238 ax = set_y_as_percent(ax) 239 return fig 240 241 242def plot_forecast_component( 243 m: Prophet, 244 fcst: pd.DataFrame, 245 name: str, 246 ax: plt.Axes | None = None, 247 uncertainty: bool = True, 248 plot_cap: bool = False, 249 figsize: tuple[int, int] = (10, 6), 250) -> Sequence[plt.Artist]: 251 """Plot a particular component of the forecast. 252 253 Parameters 254 ---------- 255 m: Prophet model. 256 fcst: pd.DataFrame output of m.predict. 257 name: Name of the component to plot. 258 ax: Optional matplotlib Axes to plot on. 259 uncertainty: Optional boolean to plot uncertainty intervals, which will 260 only be done if m.uncertainty_samples > 0. 261 plot_cap: Optional boolean indicating if the capacity should be shown 262 in the figure, if available. 263 figsize: Optional tuple width, height in inches. 264 265 Returns 266 ------- 267 a list of matplotlib artists 268 """ 269 artists = [] 270 if not ax: 271 fig = plt.figure(facecolor='w', figsize=figsize) 272 ax = fig.add_subplot(111) 273 fcst_t = fcst['ds'] 274 artists += ax.plot(fcst_t, fcst[name], ls='-', c='#0072B2') 275 if 'cap' in fcst and plot_cap: 276 artists += ax.plot(fcst_t, fcst['cap'], ls='--', c='k') 277 if m.logistic_floor and 'floor' in fcst and plot_cap: 278 ax.plot(fcst_t, fcst['floor'], ls='--', c='k') 279 if uncertainty and m.uncertainty_samples: 280 artists += [ax.fill_between( 281 fcst_t, fcst[name + '_lower'], fcst[name + '_upper'], 282 color='#0072B2', alpha=0.2)] 283 # Specify formatting to workaround matplotlib issue #12925 284 locator = AutoDateLocator(interval_multiples=False) 285 formatter = AutoDateFormatter(locator) 286 ax.xaxis.set_major_locator(locator) 287 ax.xaxis.set_major_formatter(formatter) 288 ax.grid(True, which='major', c='gray', ls='-', lw=1, alpha=0.2) 289 ax.set_xlabel('ds') 290 ax.set_ylabel(name) 291 assert m.component_modes 292 if name in m.component_modes['multiplicative']: 293 ax = set_y_as_percent(ax) 294 return artists 295 296 297def seasonality_plot_df( 298 m: Prophet, 299 ds: Sequence[pd.Timestamp] | pd.DatetimeIndex, 300) -> pd.DataFrame: 301 """Prepare dataframe for plotting seasonal components. 302 303 Parameters 304 ---------- 305 m: Prophet model. 306 ds: List of dates for column ds. 307 308 Returns 309 ------- 310 A dataframe with seasonal components on ds. 311 """ 312 df_dict = {'ds': ds, 'cap': 1., 'floor': 0.} 313 for name in m.extra_regressors: 314 df_dict[name] = 0. 315 # Activate all conditional seasonality columns 316 for props in m.seasonalities.values(): 317 if props['condition_name'] is not None: 318 df_dict[props['condition_name']] = True 319 df = pd.DataFrame(df_dict) 320 df = m.setup_dataframe(df) 321 return df 322 323 324def plot_weekly( 325 m: Prophet, 326 ax: plt.Axes | None = None, 327 uncertainty: bool = True, 328 weekly_start: int = 0, 329 figsize: tuple[int, int] = (10, 6), 330 name: str = 'weekly', 331) -> Sequence[plt.Artist]: 332 """Plot the weekly component of the forecast. 333 334 Parameters 335 ---------- 336 m: Prophet model. 337 ax: Optional matplotlib Axes to plot on. One will be created if this 338 is not provided. 339 uncertainty: Optional boolean to plot uncertainty intervals, which will 340 only be done if m.uncertainty_samples > 0. 341 weekly_start: Optional int specifying the start day of the weekly 342 seasonality plot. 0 (default) starts the week on Sunday. 1 shifts 343 by 1 day to Monday, and so on. 344 figsize: Optional tuple width, height in inches. 345 name: Name of seasonality component if changed from default 'weekly'. 346 347 Returns 348 ------- 349 a list of matplotlib artists 350 """ 351 artists = [] 352 if not ax: 353 fig = plt.figure(facecolor='w', figsize=figsize) 354 ax = fig.add_subplot(111) 355 # Compute weekly seasonality for a Sun-Sat sequence of dates. 356 days = (pd.date_range(start='2017-01-01', periods=7) + 357 pd.Timedelta(days=weekly_start)) 358 df_w = seasonality_plot_df(m, days) 359 seas = m.predict_seasonal_components(df_w) 360 days = days.day_name() 361 artists += ax.plot(range(len(days)), seas[name], ls='-', 362 c='#0072B2') 363 if uncertainty and m.uncertainty_samples: 364 artists += [ax.fill_between(range(len(days)), 365 seas[name + '_lower'], seas[name + '_upper'], 366 color='#0072B2', alpha=0.2)] 367 ax.grid(True, which='major', c='gray', ls='-', lw=1, alpha=0.2) 368 ax.set_xticks(range(len(days))) 369 ax.set_xticklabels(days) 370 ax.set_xlabel('Day of week') 371 ax.set_ylabel(name) 372 if m.seasonalities[name]['mode'] == 'multiplicative': 373 ax = set_y_as_percent(ax) 374 return artists 375 376 377def plot_yearly( 378 m: Prophet, 379 ax: plt.Axes | None = None, 380 uncertainty: bool = True, 381 yearly_start: int = 0, 382 figsize: tuple[int, int] = (10, 6), 383 name: str = 'yearly', 384) -> Sequence[plt.Artist]: 385 """Plot the yearly component of the forecast. 386 387 Parameters 388 ---------- 389 m: Prophet model. 390 ax: Optional matplotlib Axes to plot on. One will be created if 391 this is not provided. 392 uncertainty: Optional boolean to plot uncertainty intervals, which will 393 only be done if m.uncertainty_samples > 0. 394 yearly_start: Optional int specifying the start day of the yearly 395 seasonality plot. 0 (default) starts the year on Jan 1. 1 shifts 396 by 1 day to Jan 2, and so on. 397 figsize: Optional tuple width, height in inches. 398 name: Name of seasonality component if previously changed from default 'yearly'. 399 400 Returns 401 ------- 402 a list of matplotlib artists 403 """ 404 artists = [] 405 if not ax: 406 fig = plt.figure(facecolor='w', figsize=figsize) 407 ax = fig.add_subplot(111) 408 # Compute yearly seasonality for a Jan 1 - Dec 31 sequence of dates. 409 days = (pd.date_range(start='2017-01-01', periods=365) + 410 pd.Timedelta(days=yearly_start)) 411 df_y = seasonality_plot_df(m, days) 412 seas = m.predict_seasonal_components(df_y) 413 artists += ax.plot( 414 df_y['ds'], seas[name], ls='-', c='#0072B2') 415 if uncertainty and m.uncertainty_samples: 416 artists += [ax.fill_between( 417 df_y['ds'], seas[name + '_lower'], 418 seas[name + '_upper'], color='#0072B2', alpha=0.2)] 419 ax.grid(True, which='major', c='gray', ls='-', lw=1, alpha=0.2) 420 months = MonthLocator(range(1, 13), bymonthday=1, interval=2) 421 ax.xaxis.set_major_formatter(FuncFormatter( 422 lambda x, pos=None: '{dt:%B} {dt.day}'.format(dt=num2date(x)))) 423 ax.xaxis.set_major_locator(months) 424 ax.set_xlabel('Day of year') 425 ax.set_ylabel(name) 426 if m.seasonalities[name]['mode'] == 'multiplicative': 427 ax = set_y_as_percent(ax) 428 return artists 429 430 431def plot_seasonality( 432 m: Prophet, 433 name: str, 434 ax: plt.Axes | None = None, 435 uncertainty: bool = True, 436 figsize: tuple[int, int] = (10, 6), 437) -> Sequence[plt.Artist]: 438 """Plot a custom seasonal component. 439 440 Parameters 441 ---------- 442 m: Prophet model. 443 name: Seasonality name, like 'daily', 'weekly'. 444 ax: Optional matplotlib Axes to plot on. One will be created if 445 this is not provided. 446 uncertainty: Optional boolean to plot uncertainty intervals, which will 447 only be done if m.uncertainty_samples > 0. 448 figsize: Optional tuple width, height in inches. 449 450 Returns 451 ------- 452 a list of matplotlib artists 453 """ 454 artists = [] 455 if not ax: 456 fig = plt.figure(facecolor='w', figsize=figsize) 457 ax = fig.add_subplot(111) 458 # Compute seasonality from Jan 1 through a single period. 459 start = pd.to_datetime('2017-01-01 0000') 460 period = m.seasonalities[name]['period'] 461 end = start + pd.Timedelta(days=period) 462 plot_points = 200 463 # https://github.com/pandas-dev/pandas-stubs/issues/1645 464 days = pd.to_datetime(np.linspace(start.value, end.value, plot_points)) # pyrefly:ignore[no-matching-overload] 465 df_y = seasonality_plot_df(m, days) 466 seas = m.predict_seasonal_components(df_y) 467 artists += ax.plot(df_y['ds'], seas[name], ls='-', 468 c='#0072B2') 469 if uncertainty and m.uncertainty_samples: 470 artists += [ax.fill_between( 471 df_y['ds'], seas[name + '_lower'], 472 seas[name + '_upper'], color='#0072B2', alpha=0.2)] 473 ax.grid(True, which='major', c='gray', ls='-', lw=1, alpha=0.2) 474 n_ticks = 8 475 # https://github.com/pandas-dev/pandas-stubs/issues/1645 476 xticks = pd.to_datetime(np.linspace(start.value, end.value, n_ticks) # pyrefly:ignore[no-matching-overload] 477 ).to_pydatetime() 478 ax.set_xticks(xticks) 479 if name == 'yearly': 480 fmt = FuncFormatter( 481 lambda x, pos=None: '{dt:%B} {dt.day}'.format(dt=num2date(x))) 482 ax.set_xlabel('Day of year') 483 elif name == 'weekly': 484 fmt = FuncFormatter( 485 lambda x, pos=None: '{dt:%A}'.format(dt=num2date(x))) 486 ax.set_xlabel('Day of Week') 487 elif name == 'daily': 488 fmt = FuncFormatter( 489 lambda x, pos=None: '{dt:%T}'.format(dt=num2date(x))) 490 ax.set_xlabel('Hour of day') 491 elif period <= 2: 492 fmt = FuncFormatter( 493 lambda x, pos=None: '{dt:%T}'.format(dt=num2date(x))) 494 ax.set_xlabel('Hours') 495 else: 496 fmt = FuncFormatter( 497 lambda x, pos=None: '{:.0f}'.format(pos * period / (n_ticks - 1))) 498 ax.set_xlabel('Days') 499 ax.xaxis.set_major_formatter(fmt) 500 ax.set_ylabel(name) 501 if m.seasonalities[name]['mode'] == 'multiplicative': 502 ax = set_y_as_percent(ax) 503 return artists 504 505 506def set_y_as_percent(ax: _AxT) -> _AxT: 507 yticks = 100 * ax.get_yticks() 508 yticklabels = ['{0:.4g}%'.format(y) for y in yticks] 509 ax.set_yticks(ax.get_yticks().tolist()) 510 ax.set_yticklabels(yticklabels) 511 return ax 512 513 514def add_changepoints_to_plot( 515 ax: plt.Axes, 516 m: Prophet, 517 fcst: pd.DataFrame, 518 threshold: float = 0.01, 519 cp_color: str = 'r', 520 cp_linestyle: str = '--', 521 trend: bool = True, 522) -> list[plt.Line2D]: 523 """Add markers for significant changepoints to prophet forecast plot. 524 525 Example: 526 fig = m.plot(forecast) 527 add_changepoints_to_plot(fig.gca(), m, forecast) 528 529 Parameters 530 ---------- 531 ax: axis on which to overlay changepoint markers. 532 m: Prophet model. 533 fcst: Forecast output from m.predict. 534 threshold: Threshold on trend change magnitude for significance. 535 cp_color: Color of changepoint markers. 536 cp_linestyle: Linestyle for changepoint markers. 537 trend: If True, will also overlay the trend. 538 539 Returns 540 ------- 541 a list of matplotlib artists 542 """ 543 artists = [] 544 if trend: 545 artists.extend(ax.plot(fcst['ds'], fcst['trend'], c=cp_color)) 546 547 assert m.changepoints is not None 548 signif_changepoints = m.changepoints[ 549 np.abs(np.nanmean(m.params['delta'], axis=0)) >= threshold 550 ] if len(m.changepoints) > 0 else [] 551 for cp in signif_changepoints: 552 # Matplotlib stubs type axvline x as float; pandas Timestamp is accepted at runtime. 553 artists.append(ax.axvline(x=cp, c=cp_color, ls=cp_linestyle)) # pyrefly:ignore[bad-argument-type] 554 return artists 555 556 557def plot_cross_validation_metric( 558 df_cv: pd.DataFrame, 559 metric: str, 560 rolling_window: float = 0.1, 561 ax: plt.Axes | None = None, 562 figsize: tuple[int, int] = (10, 6), 563 color: str = 'b', 564 point_color: str = 'gray', 565) -> plt.Figure: 566 """Plot a performance metric vs. forecast horizon from cross validation. 567 568 Cross validation produces a collection of out-of-sample model predictions 569 that can be compared to actual values, at a range of different horizons 570 (distance from the cutoff). This computes a specified performance metric 571 for each prediction, and aggregated over a rolling window with horizon. 572 573 This uses prophet.diagnostics.performance_metrics to compute the metrics. 574 Valid values of metric are 'mse', 'rmse', 'mae', 'mape', 'mdape', 'smape', and 'coverage'. 575 576 rolling_window is the proportion of data included in the rolling window of 577 aggregation. The default value of 0.1 means 10% of data are included in the 578 aggregation for computing the metric. 579 580 As a concrete example, if metric='mse', then this plot will show the 581 squared error for each cross validation prediction, along with the MSE 582 averaged over rolling windows of 10% of the data. 583 584 Parameters 585 ---------- 586 df_cv: The output from prophet.diagnostics.cross_validation. 587 metric: Metric name, one of ['mse', 'rmse', 'mae', 'mape', 'mdape', 'smape', 'coverage']. 588 rolling_window: Proportion of data to use for rolling average of metric. 589 In [0, 1]. Defaults to 0.1. 590 ax: Optional matplotlib axis on which to plot. If not given, a new figure 591 will be created. 592 figsize: Optional tuple width, height in inches. 593 color: Optional color for plot and error points, useful when plotting 594 multiple model performances on one axis for comparison. 595 596 Returns 597 ------- 598 a matplotlib figure. 599 """ 600 if ax is None: 601 fig = plt.figure(facecolor='w', figsize=figsize) 602 ax = fig.add_subplot(111) 603 else: 604 fig = cast('plt.Figure', ax.get_figure()) 605 # Get the metric at the level of individual predictions, and with the rolling window. 606 df_none = performance_metrics(df_cv, metrics=[metric], rolling_window=-1) 607 df_h = performance_metrics(df_cv, metrics=[metric], rolling_window=rolling_window) 608 609 assert df_none is not None 610 assert df_h is not None 611 612 # Some work because matplotlib does not handle timedelta 613 # Target ~10 ticks. 614 tick_w = max(df_none['horizon'].astype('timedelta64[ns]')) / 10. 615 # Find the largest time resolution that has <1 unit per bin. 616 dts: list[Literal["D", "h", "m", "s", "ms", "us", "ns"]] 617 dts = ['D', 'h', 'm', 's', 'ms', 'us', 'ns'] 618 dt_names = [ 619 'days', 'hours', 'minutes', 'seconds', 'milliseconds', 'microseconds', 620 'nanoseconds' 621 ] 622 dt_conversions = [ 623 24 * 60 * 60 * 10 ** 9, 624 60 * 60 * 10 ** 9, 625 60 * 10 ** 9, 626 10 ** 9, 627 10 ** 6, 628 10 ** 3, 629 1., 630 ] 631 for i, dt in enumerate(dts): 632 if np.timedelta64(1, dt) < np.timedelta64(tick_w, 'ns'): 633 break 634 635 x_plt = np.asarray(df_none['horizon'].astype('timedelta64[ns]')).view(np.int64) / float(dt_conversions[i]) 636 x_plt_h = np.asarray(df_h['horizon'].astype('timedelta64[ns]')).view(np.int64) / float(dt_conversions[i]) 637 638 ax.plot(x_plt, df_none[metric], '.', alpha=0.1, c=point_color) 639 ax.plot(x_plt_h, df_h[metric], '-', c=color) 640 ax.grid(True) 641 642 ax.set_xlabel('Horizon ({})'.format(dt_names[i])) 643 ax.set_ylabel(metric) 644 return fig 645 646 647def plot_plotly( 648 m: Prophet, 649 fcst: pd.DataFrame, 650 uncertainty: bool = True, 651 plot_cap: bool = True, 652 trend: bool = False, 653 changepoints: bool = False, 654 changepoints_threshold: float = 0.01, 655 xlabel: str = 'ds', 656 ylabel: str = 'y', 657 figsize: tuple[int, int] = (900, 600) 658) -> go.Figure: 659 """Plot the Prophet forecast with Plotly offline. 660 661 Plotting in Jupyter Notebook requires initializing plotly.offline.init_notebook_mode(): 662 >>> import plotly.offline as py 663 >>> py.init_notebook_mode() 664 Then the figure can be displayed using plotly.offline.iplot(...): 665 >>> fig = plot_plotly(m, fcst) 666 >>> py.iplot(fig) 667 see https://plot.ly/python/offline/ for details 668 669 Parameters 670 ---------- 671 m: Prophet model. 672 fcst: pd.DataFrame output of m.predict. 673 uncertainty: Optional boolean to plot uncertainty intervals. 674 plot_cap: Optional boolean indicating if the capacity should be shown 675 in the figure, if available. 676 trend: Optional boolean to plot trend 677 changepoints: Optional boolean to plot changepoints 678 changepoints_threshold: Threshold on trend change magnitude for significance. 679 xlabel: Optional label name on X-axis 680 ylabel: Optional label name on Y-axis 681 figsize: The plot's size (in px). 682 683 Returns 684 ------- 685 A Plotly Figure. 686 """ 687 prediction_color = '#0072B2' 688 error_color = 'rgba(0, 114, 178, 0.2)' # '#0072B2' with 0.2 opacity 689 actual_color = 'black' 690 cap_color = 'black' 691 trend_color = '#B23B00' 692 line_width = 2 693 marker_size = 4 694 695 data = [] 696 # Add actual 697 assert m.history 698 data.append(go.Scatter( 699 name='Actual', 700 x=m.history['ds'], 701 y=m.history['y'], 702 marker=dict(color=actual_color, size=marker_size), 703 mode='markers' 704 )) 705 # Add lower bound 706 if uncertainty and m.uncertainty_samples: 707 data.append(go.Scatter( 708 x=fcst['ds'], 709 y=fcst['yhat_lower'], 710 mode='lines', 711 line=dict(width=0), 712 hoverinfo='skip' 713 )) 714 # Add prediction 715 data.append(go.Scatter( 716 name='Predicted', 717 x=fcst['ds'], 718 y=fcst['yhat'], 719 mode='lines', 720 line=dict(color=prediction_color, width=line_width), 721 fillcolor=error_color, 722 fill='tonexty' if uncertainty and m.uncertainty_samples else 'none' 723 )) 724 # Add upper bound 725 if uncertainty and m.uncertainty_samples: 726 data.append(go.Scatter( 727 x=fcst['ds'], 728 y=fcst['yhat_upper'], 729 mode='lines', 730 line=dict(width=0), 731 fillcolor=error_color, 732 fill='tonexty', 733 hoverinfo='skip' 734 )) 735 # Add caps 736 if 'cap' in fcst and plot_cap: 737 data.append(go.Scatter( 738 name='Cap', 739 x=fcst['ds'], 740 y=fcst['cap'], 741 mode='lines', 742 line=dict(color=cap_color, dash='dash', width=line_width), 743 )) 744 if m.logistic_floor and 'floor' in fcst and plot_cap: 745 data.append(go.Scatter( 746 name='Floor', 747 x=fcst['ds'], 748 y=fcst['floor'], 749 mode='lines', 750 line=dict(color=cap_color, dash='dash', width=line_width), 751 )) 752 # Add trend 753 if trend: 754 data.append(go.Scatter( 755 name='Trend', 756 x=fcst['ds'], 757 y=fcst['trend'], 758 mode='lines', 759 line=dict(color=trend_color, width=line_width), 760 )) 761 # Add changepoints 762 assert m.changepoints 763 if changepoints and len(m.changepoints) > 0: 764 signif_changepoints = m.changepoints[ 765 np.abs(np.nanmean(m.params['delta'], axis=0)) >= changepoints_threshold 766 ] 767 data.append(go.Scatter( 768 x=signif_changepoints, 769 y=fcst.loc[fcst['ds'].isin(signif_changepoints), 'trend'], 770 marker=dict(size=50, symbol='line-ns-open', color=trend_color, 771 line=dict(width=line_width)), 772 mode='markers', 773 hoverinfo='skip' 774 )) 775 776 layout = dict( 777 showlegend=False, 778 width=figsize[0], 779 height=figsize[1], 780 yaxis=dict( 781 title=ylabel 782 ), 783 xaxis=dict( 784 title=xlabel, 785 type='date', 786 rangeselector=dict( 787 buttons=list([ 788 dict(count=7, 789 label='1w', 790 step='day', 791 stepmode='backward'), 792 dict(count=1, 793 label='1m', 794 step='month', 795 stepmode='backward'), 796 dict(count=6, 797 label='6m', 798 step='month', 799 stepmode='backward'), 800 dict(count=1, 801 label='1y', 802 step='year', 803 stepmode='backward'), 804 dict(step='all') 805 ]) 806 ), 807 rangeslider=dict( 808 visible=True 809 ), 810 ), 811 ) 812 fig = go.Figure(data=data, layout=layout) 813 return fig 814 815 816def plot_components_plotly( 817 m: Prophet, 818 fcst: pd.DataFrame, 819 uncertainty: bool = True, 820 plot_cap: bool = True, 821 figsize: tuple[int, int] = (900, 200), 822) -> go.Figure: 823 """Plot the Prophet forecast components using Plotly. 824 See plot_plotly() for Plotly setup instructions 825 826 Will plot whichever are available of: trend, holidays, weekly 827 seasonality, yearly seasonality, and additive and multiplicative extra 828 regressors. 829 830 Parameters 831 ---------- 832 m: Prophet model. 833 fcst: pd.DataFrame output of m.predict. 834 uncertainty: Optional boolean to plot uncertainty intervals, which will 835 only be done if m.uncertainty_samples > 0. 836 plot_cap: Optional boolean indicating if the capacity should be shown 837 in the figure, if available. 838 figsize: Set the size for the subplots (in px). 839 840 Returns 841 ------- 842 A Plotly Figure. 843 """ 844 845 # Identify components to plot and get their Plotly props 846 components = {} 847 components['trend'] = get_forecast_component_plotly_props( 848 m, fcst, 'trend', uncertainty, plot_cap) 849 if m.train_holiday_names is not None and 'holidays' in fcst: 850 components['holidays'] = get_forecast_component_plotly_props( 851 m, fcst, 'holidays', uncertainty) 852 853 regressors = {'additive': False, 'multiplicative': False} 854 for name, props in m.extra_regressors.items(): 855 regressors[props['mode']] = True 856 for mode in ['additive', 'multiplicative']: 857 if regressors[mode] and 'extra_regressors_{}'.format(mode) in fcst: 858 components['extra_regressors_{}'.format(mode)] = get_forecast_component_plotly_props( 859 m, fcst, 'extra_regressors_{}'.format(mode)) 860 for seasonality in m.seasonalities: 861 components[seasonality] = get_seasonality_plotly_props(m, seasonality) 862 863 # Create Plotly subplot figure and add the components to it 864 fig = make_subplots(rows=len(components), cols=1, print_grid=False) 865 fig['layout'].update(go.Layout( 866 showlegend=False, 867 width=figsize[0], 868 height=figsize[1] * len(components) 869 )) 870 for i, name in enumerate(components): 871 if i == 0: 872 xaxis = fig['layout']['xaxis'] 873 yaxis = fig['layout']['yaxis'] 874 else: 875 xaxis = fig['layout']['xaxis{}'.format(i + 1)] 876 yaxis = fig['layout']['yaxis{}'.format(i + 1)] 877 xaxis.update(components[name]['xaxis']) 878 yaxis.update(components[name]['yaxis']) 879 for trace in components[name]['traces']: 880 fig.append_trace(trace, i + 1, 1) 881 return fig 882 883 884def plot_forecast_component_plotly( 885 m: Prophet, 886 fcst: pd.DataFrame, 887 name: str, 888 uncertainty: bool = True, 889 plot_cap: bool = False, 890 figsize: tuple[int, int] = (900, 300) 891) -> go.Figure: 892 """Plot a particular component of the forecast using Plotly. 893 See plot_plotly() for Plotly setup instructions 894 895 Parameters 896 ---------- 897 m: Prophet model. 898 fcst: pd.DataFrame output of m.predict. 899 name: Name of the component to plot. 900 uncertainty: Optional boolean to plot uncertainty intervals, which will 901 only be done if m.uncertainty_samples > 0. 902 plot_cap: Optional boolean indicating if the capacity should be shown 903 in the figure, if available. 904 figsize: The plot's size (in px). 905 906 Returns 907 ------- 908 A Plotly Figure. 909 """ 910 props = get_forecast_component_plotly_props(m, fcst, name, uncertainty, plot_cap) 911 layout = go.Layout( 912 width=figsize[0], 913 height=figsize[1], 914 showlegend=False, 915 xaxis=props['xaxis'], 916 yaxis=props['yaxis'] 917 ) 918 fig = go.Figure(data=props['traces'], layout=layout) 919 return fig 920 921 922def plot_seasonality_plotly( 923 m: Prophet, 924 name: str, 925 uncertainty: bool = True, 926 figsize: tuple[int, int] = (900, 300) 927) -> go.Figure: 928 """Plot a custom seasonal component using Plotly. 929 See plot_plotly() for Plotly setup instructions 930 931 Parameters 932 ---------- 933 m: Prophet model. 934 name: Seasonality name, like 'daily', 'weekly'. 935 uncertainty: Optional boolean to plot uncertainty intervals, which will 936 only be done if m.uncertainty_samples > 0. 937 figsize: Set the plot's size (in px). 938 939 Returns 940 ------- 941 A Plotly Figure. 942 """ 943 props = get_seasonality_plotly_props(m, name, uncertainty) 944 layout = go.Layout( 945 width=figsize[0], 946 height=figsize[1], 947 showlegend=False, 948 xaxis=props['xaxis'], 949 yaxis=props['yaxis'] 950 ) 951 fig = go.Figure(data=props['traces'], layout=layout) 952 return fig 953 954 955def get_forecast_component_plotly_props( 956 m: Prophet, 957 fcst: pd.DataFrame, 958 name: str, 959 uncertainty: bool = True, 960 plot_cap: bool = False, 961) -> _PlotlyProps: 962 """Prepares a dictionary for plotting the selected forecast component with Plotly 963 964 Parameters 965 ---------- 966 m: Prophet model. 967 fcst: pd.DataFrame output of m.predict. 968 name: Name of the component to plot. 969 uncertainty: Optional boolean to plot uncertainty intervals, which will 970 only be done if m.uncertainty_samples > 0. 971 plot_cap: Optional boolean indicating if the capacity should be shown 972 in the figure, if available. 973 974 Returns 975 ------- 976 A dictionary with Plotly traces, xaxis and yaxis 977 """ 978 prediction_color = '#0072B2' 979 error_color = 'rgba(0, 114, 178, 0.2)' # '#0072B2' with 0.2 opacity 980 cap_color = 'black' 981 zeroline_color = '#AAA' 982 line_width = 2 983 984 range_margin = (fcst['ds'].max() - fcst['ds'].min()) * 0.05 985 range_x = [fcst['ds'].min() - range_margin, fcst['ds'].max() + range_margin] 986 987 text = None 988 mode = 'lines' 989 if name == 'holidays': 990 991 # Combine holidays into one hover text 992 holidays = m.construct_holiday_dataframe(fcst['ds']) 993 holiday_features, _, _ = m.make_holiday_features(fcst['ds'], holidays) 994 holiday_features.columns = holiday_features.columns.str.replace('_delim_', '', regex=False) 995 holiday_features.columns = holiday_features.columns.str.replace('+0', '', regex=False) 996 text = pd.Series(data='', index=holiday_features.index) 997 for holiday_feature, idxs in holiday_features.items(): 998 # https://github.com/facebook/pyrefly/issues/2248 999 # pyrefly:ignore[unsupported-operation] 1000 text[idxs.astype(bool) & (text != '')] += '<br>' # Add newline if additional holiday 1001 text[idxs.astype(bool)] += holiday_feature # pyrefly:ignore[unsupported-operation] 1002 1003 traces = [] 1004 traces.append(go.Scatter( 1005 name=name, 1006 x=fcst['ds'], 1007 y=fcst[name], 1008 mode=mode, 1009 line=go.scatter.Line(color=prediction_color, width=line_width), 1010 text=text, 1011 )) 1012 if uncertainty and m.uncertainty_samples and (fcst[name + '_upper'] != fcst[name + '_lower']).any(): 1013 if mode == 'markers': 1014 traces[0].update( 1015 error_y=dict( 1016 type='data', 1017 symmetric=False, 1018 array=fcst[name + '_upper'], 1019 arrayminus=fcst[name + '_lower'], 1020 width=0, 1021 color=error_color 1022 ) 1023 ) 1024 else: 1025 traces.append(go.Scatter( 1026 name=name + '_upper', 1027 x=fcst['ds'], 1028 y=fcst[name + '_upper'], 1029 mode=mode, 1030 line=go.scatter.Line(width=0, color=error_color) 1031 )) 1032 traces.append(go.Scatter( 1033 name=name + '_lower', 1034 x=fcst['ds'], 1035 y=fcst[name + '_lower'], 1036 mode=mode, 1037 line=go.scatter.Line(width=0, color=error_color), 1038 fillcolor=error_color, 1039 fill='tonexty' 1040 )) 1041 if 'cap' in fcst and plot_cap: 1042 traces.append(go.Scatter( 1043 name='Cap', 1044 x=fcst['ds'], 1045 y=fcst['cap'], 1046 mode='lines', 1047 line=go.scatter.Line(color=cap_color, dash='dash', width=line_width), 1048 )) 1049 if m.logistic_floor and 'floor' in fcst and plot_cap: 1050 traces.append(go.Scatter( 1051 name='Floor', 1052 x=fcst['ds'], 1053 y=fcst['floor'], 1054 mode='lines', 1055 line=go.scatter.Line(color=cap_color, dash='dash', width=line_width), 1056 )) 1057 1058 xaxis = go.layout.XAxis( 1059 type='date', 1060 range=range_x) 1061 yaxis = go.layout.YAxis(rangemode='normal' if name == 'trend' else 'tozero', 1062 title=go.layout.yaxis.Title(text=name), 1063 zerolinecolor=zeroline_color) 1064 assert m.component_modes 1065 if name in m.component_modes['multiplicative']: 1066 yaxis.update(tickformat='%', hoverformat='.2%') 1067 return {'traces': traces, 'xaxis': xaxis, 'yaxis': yaxis} 1068 1069 1070def get_seasonality_plotly_props( 1071 m: Prophet, 1072 name: str, 1073 uncertainty: bool = True, 1074) -> _PlotlyProps: 1075 """Prepares a dictionary for plotting the selected seasonality with Plotly 1076 1077 Parameters 1078 ---------- 1079 m: Prophet model. 1080 name: Name of the component to plot. 1081 uncertainty: Optional boolean to plot uncertainty intervals, which will 1082 only be done if m.uncertainty_samples > 0. 1083 1084 Returns 1085 ------- 1086 A dictionary with Plotly traces, xaxis and yaxis 1087 """ 1088 prediction_color = '#0072B2' 1089 error_color = 'rgba(0, 114, 178, 0.2)' # '#0072B2' with 0.2 opacity 1090 line_width = 2 1091 zeroline_color = '#AAA' 1092 1093 # Compute seasonality from Jan 1 through a single period. 1094 start = pd.to_datetime('2017-01-01 0000') 1095 period = m.seasonalities[name]['period'] 1096 end = start + pd.Timedelta(days=period) 1097 assert m.history is not None 1098 if (m.history['ds'].dt.hour == 0).all(): # Day Precision 1099 plot_points = np.floor(period).astype(int) 1100 elif (m.history['ds'].dt.minute == 0).all(): # Hour Precision 1101 plot_points = np.floor(period * 24).astype(int) 1102 else: # Minute Precision 1103 plot_points = np.floor(period * 24 * 60).astype(int) 1104 days = pd.to_datetime(np.linspace(start.value, end.value, plot_points, endpoint=False)) 1105 df_y = seasonality_plot_df(m, days) 1106 seas = m.predict_seasonal_components(df_y) 1107 1108 traces = [] 1109 traces.append(go.Scatter( 1110 name=name, 1111 x=df_y['ds'], 1112 y=seas[name], 1113 mode='lines', 1114 line=go.scatter.Line(color=prediction_color, width=line_width) 1115 )) 1116 if uncertainty and m.uncertainty_samples and (seas[name + '_upper'] != seas[name + '_lower']).any(): 1117 traces.append(go.Scatter( 1118 name=name + '_upper', 1119 x=df_y['ds'], 1120 y=seas[name + '_upper'], 1121 mode='lines', 1122 line=go.scatter.Line(width=0, color=error_color) 1123 )) 1124 traces.append(go.Scatter( 1125 name=name + '_lower', 1126 x=df_y['ds'], 1127 y=seas[name + '_lower'], 1128 mode='lines', 1129 line=go.scatter.Line(width=0, color=error_color), 1130 fillcolor=error_color, 1131 fill='tonexty' 1132 )) 1133 1134 # Set tick formats (examples are based on 2017-01-06 21:15) 1135 if period <= 2: 1136 tickformat = '%H:%M' # "21:15" 1137 elif period < 7: 1138 tickformat = '%A %H:%M' # "Friday 21:15" 1139 elif period < 14: 1140 tickformat = '%A' # "Friday" 1141 else: 1142 tickformat = '%B %e' # "January 6" 1143 1144 range_margin = (df_y['ds'].max() - df_y['ds'].min()) * 0.05 1145 xaxis = go.layout.XAxis( 1146 tickformat=tickformat, 1147 type='date', 1148 range=[df_y['ds'].min() - range_margin, df_y['ds'].max() + range_margin] 1149 ) 1150 1151 yaxis = go.layout.YAxis(title=go.layout.yaxis.Title(text=name), 1152 zerolinecolor=zeroline_color) 1153 if m.seasonalities[name]['mode'] == 'multiplicative': 1154 yaxis.update(tickformat='%', hoverformat='.2%') 1155 1156 return {'traces': traces, 'xaxis': xaxis, 'yaxis': yaxis}
61def plot( 62 m: Prophet, 63 fcst: pd.DataFrame, 64 ax: plt.Axes | None = None, 65 uncertainty: bool = True, 66 plot_cap: bool = True, 67 xlabel: str = "ds", 68 ylabel: str = "y", 69 figsize: tuple[int, int] = (10, 6), 70 include_legend: bool = False, 71) -> plt.Figure: 72 """Plot the Prophet forecast. 73 74 Parameters 75 ---------- 76 m: Prophet model. 77 fcst: pd.DataFrame output of m.predict. 78 ax: Optional matplotlib axes on which to plot. 79 uncertainty: Optional boolean to plot uncertainty intervals, which will 80 only be done if m.uncertainty_samples > 0. 81 plot_cap: Optional boolean indicating if the capacity should be shown 82 in the figure, if available. 83 xlabel: Optional label name on X-axis 84 ylabel: Optional label name on Y-axis 85 figsize: Optional tuple width, height in inches. 86 include_legend: Optional boolean to add legend to the plot. 87 88 Returns 89 ------- 90 A matplotlib figure. 91 """ 92 user_provided_ax = False if ax is None else True 93 if ax is None: 94 fig = plt.figure(facecolor='w', figsize=figsize) 95 ax = fig.add_subplot(111) 96 else: 97 fig = cast('plt.Figure', ax.get_figure()) 98 fcst_t = fcst['ds'] 99 history = cast('pd.DataFrame', m.history) 100 ax.plot(history['ds'], history['y'], 'k.', label='Observed data points') 101 ax.plot(fcst_t, fcst['yhat'], ls='-', c='#0072B2', label='Forecast') 102 if 'cap' in fcst and plot_cap: 103 ax.plot(fcst_t, fcst['cap'], ls='--', c='k', label='Maximum capacity') 104 if m.logistic_floor and 'floor' in fcst and plot_cap: 105 ax.plot(fcst_t, fcst['floor'], ls='--', c='k', label='Minimum capacity') 106 if uncertainty and m.uncertainty_samples: 107 ax.fill_between(fcst_t, fcst['yhat_lower'], fcst['yhat_upper'], 108 color='#0072B2', alpha=0.2, label='Uncertainty interval') 109 # Specify formatting to workaround matplotlib issue #12925 110 locator = AutoDateLocator(interval_multiples=False) 111 formatter = AutoDateFormatter(locator) 112 ax.xaxis.set_major_locator(locator) 113 ax.xaxis.set_major_formatter(formatter) 114 ax.grid(True, which='major', c='gray', ls='-', lw=1, alpha=0.2) 115 ax.set_xlabel(xlabel) 116 ax.set_ylabel(ylabel) 117 if include_legend: 118 ax.legend() 119 if not user_provided_ax: 120 try: 121 if fig.get_layout_engine() is None: 122 fig.tight_layout() 123 except AttributeError: 124 fig.tight_layout() 125 return fig
Plot the Prophet forecast.
Parameters
m (Prophet model.):
fcst (pd.DataFrame output of m.predict.):
ax (Optional matplotlib axes on which to plot.):
uncertainty (Optional boolean to plot uncertainty intervals, which will): only be done if m.uncertainty_samples > 0.
- plot_cap (Optional boolean indicating if the capacity should be shown): in the figure, if available.
xlabel (Optional label name on X-axis):
ylabel (Optional label name on Y-axis):
figsize (Optional tuple width, height in inches.):
include_legend (Optional boolean to add legend to the plot.):
Returns
- A matplotlib figure.
128def plot_components( 129 m: Prophet, 130 fcst: pd.DataFrame, 131 uncertainty: bool = True, 132 plot_cap: bool = True, 133 weekly_start: int = 0, 134 yearly_start: int = 0, 135 figsize: tuple[int, int] | None = None, 136) -> plt.Figure: 137 """Plot the Prophet forecast components. 138 139 Will plot whichever are available of: trend, holidays, weekly 140 seasonality, yearly seasonality, and additive and multiplicative extra 141 regressors. 142 143 Parameters 144 ---------- 145 m: Prophet model. 146 fcst: pd.DataFrame output of m.predict. 147 uncertainty: Optional boolean to plot uncertainty intervals, which will 148 only be done if m.uncertainty_samples > 0. 149 plot_cap: Optional boolean indicating if the capacity should be shown 150 in the figure, if available. 151 weekly_start: Optional int specifying the start day of the weekly 152 seasonality plot. 0 (default) starts the week on Sunday. 1 shifts 153 by 1 day to Monday, and so on. 154 yearly_start: Optional int specifying the start day of the yearly 155 seasonality plot. 0 (default) starts the year on Jan 1. 1 shifts 156 by 1 day to Jan 2, and so on. 157 figsize: Optional tuple width, height in inches. 158 159 Returns 160 ------- 161 A matplotlib figure. 162 """ 163 # Identify components to be plotted 164 components = ['trend'] 165 if m.train_holiday_names is not None and 'holidays' in fcst: 166 components.append('holidays') 167 # Plot weekly seasonality, if present 168 if 'weekly' in m.seasonalities and 'weekly' in fcst: 169 components.append('weekly') 170 # Yearly if present 171 if 'yearly' in m.seasonalities and 'yearly' in fcst: 172 components.append('yearly') 173 # Other seasonalities 174 components.extend([ 175 name for name in sorted(m.seasonalities) 176 if name in fcst and name not in ['weekly', 'yearly'] 177 ]) 178 regressors = {'additive': False, 'multiplicative': False} 179 for name, props in m.extra_regressors.items(): 180 regressors[props['mode']] = True 181 for mode in ['additive', 'multiplicative']: 182 if regressors[mode] and 'extra_regressors_{}'.format(mode) in fcst: 183 components.append('extra_regressors_{}'.format(mode)) 184 npanel = len(components) 185 186 figsize = figsize if figsize else (9, 3 * npanel) 187 fig, axes = plt.subplots(npanel, 1, facecolor='w', figsize=figsize) 188 189 if npanel == 1: 190 axes = [axes] 191 192 multiplicative_axes = [] 193 194 dt = cast('pd.DataFrame', m.history)['ds'].diff() 195 min_dt = dt.iloc[cast('np.ndarray', dt.values).nonzero()[0]].min() 196 197 for ax, plot_name in zip(axes, components): 198 if plot_name == 'trend': 199 plot_forecast_component( 200 m=m, fcst=fcst, name='trend', ax=ax, uncertainty=uncertainty, 201 plot_cap=plot_cap, 202 ) 203 elif plot_name in m.seasonalities: 204 if ( 205 (plot_name == 'weekly' or m.seasonalities[plot_name]['period'] == 7) 206 and (min_dt == pd.Timedelta(days=1)) 207 ): 208 plot_weekly( 209 m=m, name=plot_name, ax=ax, uncertainty=uncertainty, weekly_start=weekly_start 210 ) 211 elif plot_name == 'yearly' or m.seasonalities[plot_name]['period'] == 365.25: 212 plot_yearly( 213 m=m, name=plot_name, ax=ax, uncertainty=uncertainty, yearly_start=yearly_start 214 ) 215 else: 216 plot_seasonality( 217 m=m, name=plot_name, ax=ax, uncertainty=uncertainty, 218 ) 219 elif plot_name in [ 220 'holidays', 221 'extra_regressors_additive', 222 'extra_regressors_multiplicative', 223 ]: 224 plot_forecast_component( 225 m=m, fcst=fcst, name=plot_name, ax=ax, uncertainty=uncertainty, 226 plot_cap=False, 227 ) 228 assert m.component_modes is not None 229 if plot_name in m.component_modes['multiplicative']: 230 multiplicative_axes.append(ax) 231 232 try: 233 if fig.get_layout_engine() is None: 234 fig.tight_layout() 235 except AttributeError: 236 fig.tight_layout() 237 # Reset multiplicative axes labels after tight_layout adjustment 238 for ax in multiplicative_axes: 239 ax = set_y_as_percent(ax) 240 return fig
Plot the Prophet forecast components.
Will plot whichever are available of: trend, holidays, weekly seasonality, yearly seasonality, and additive and multiplicative extra regressors.
Parameters
m (Prophet model.):
fcst (pd.DataFrame output of m.predict.):
uncertainty (Optional boolean to plot uncertainty intervals, which will): only be done if m.uncertainty_samples > 0.
- plot_cap (Optional boolean indicating if the capacity should be shown): in the figure, if available.
- weekly_start (Optional 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 (Optional 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 (Optional tuple width, height in inches.):
Returns
- A matplotlib figure.
243def plot_forecast_component( 244 m: Prophet, 245 fcst: pd.DataFrame, 246 name: str, 247 ax: plt.Axes | None = None, 248 uncertainty: bool = True, 249 plot_cap: bool = False, 250 figsize: tuple[int, int] = (10, 6), 251) -> Sequence[plt.Artist]: 252 """Plot a particular component of the forecast. 253 254 Parameters 255 ---------- 256 m: Prophet model. 257 fcst: pd.DataFrame output of m.predict. 258 name: Name of the component to plot. 259 ax: Optional matplotlib Axes to plot on. 260 uncertainty: Optional boolean to plot uncertainty intervals, which will 261 only be done if m.uncertainty_samples > 0. 262 plot_cap: Optional boolean indicating if the capacity should be shown 263 in the figure, if available. 264 figsize: Optional tuple width, height in inches. 265 266 Returns 267 ------- 268 a list of matplotlib artists 269 """ 270 artists = [] 271 if not ax: 272 fig = plt.figure(facecolor='w', figsize=figsize) 273 ax = fig.add_subplot(111) 274 fcst_t = fcst['ds'] 275 artists += ax.plot(fcst_t, fcst[name], ls='-', c='#0072B2') 276 if 'cap' in fcst and plot_cap: 277 artists += ax.plot(fcst_t, fcst['cap'], ls='--', c='k') 278 if m.logistic_floor and 'floor' in fcst and plot_cap: 279 ax.plot(fcst_t, fcst['floor'], ls='--', c='k') 280 if uncertainty and m.uncertainty_samples: 281 artists += [ax.fill_between( 282 fcst_t, fcst[name + '_lower'], fcst[name + '_upper'], 283 color='#0072B2', alpha=0.2)] 284 # Specify formatting to workaround matplotlib issue #12925 285 locator = AutoDateLocator(interval_multiples=False) 286 formatter = AutoDateFormatter(locator) 287 ax.xaxis.set_major_locator(locator) 288 ax.xaxis.set_major_formatter(formatter) 289 ax.grid(True, which='major', c='gray', ls='-', lw=1, alpha=0.2) 290 ax.set_xlabel('ds') 291 ax.set_ylabel(name) 292 assert m.component_modes 293 if name in m.component_modes['multiplicative']: 294 ax = set_y_as_percent(ax) 295 return artists
Plot a particular component of the forecast.
Parameters
m (Prophet model.):
fcst (pd.DataFrame output of m.predict.):
name (Name of the component to plot.):
ax (Optional matplotlib Axes to plot on.):
uncertainty (Optional boolean to plot uncertainty intervals, which will): only be done if m.uncertainty_samples > 0.
- plot_cap (Optional boolean indicating if the capacity should be shown): in the figure, if available.
- figsize (Optional tuple width, height in inches.):
Returns
- a list of matplotlib artists
298def seasonality_plot_df( 299 m: Prophet, 300 ds: Sequence[pd.Timestamp] | pd.DatetimeIndex, 301) -> pd.DataFrame: 302 """Prepare dataframe for plotting seasonal components. 303 304 Parameters 305 ---------- 306 m: Prophet model. 307 ds: List of dates for column ds. 308 309 Returns 310 ------- 311 A dataframe with seasonal components on ds. 312 """ 313 df_dict = {'ds': ds, 'cap': 1., 'floor': 0.} 314 for name in m.extra_regressors: 315 df_dict[name] = 0. 316 # Activate all conditional seasonality columns 317 for props in m.seasonalities.values(): 318 if props['condition_name'] is not None: 319 df_dict[props['condition_name']] = True 320 df = pd.DataFrame(df_dict) 321 df = m.setup_dataframe(df) 322 return df
Prepare dataframe for plotting seasonal components.
Parameters
m (Prophet model.):
ds (List of dates for column ds.):
Returns
- A dataframe with seasonal components on ds.
325def plot_weekly( 326 m: Prophet, 327 ax: plt.Axes | None = None, 328 uncertainty: bool = True, 329 weekly_start: int = 0, 330 figsize: tuple[int, int] = (10, 6), 331 name: str = 'weekly', 332) -> Sequence[plt.Artist]: 333 """Plot the weekly component of the forecast. 334 335 Parameters 336 ---------- 337 m: Prophet model. 338 ax: Optional matplotlib Axes to plot on. One will be created if this 339 is not provided. 340 uncertainty: Optional boolean to plot uncertainty intervals, which will 341 only be done if m.uncertainty_samples > 0. 342 weekly_start: Optional int specifying the start day of the weekly 343 seasonality plot. 0 (default) starts the week on Sunday. 1 shifts 344 by 1 day to Monday, and so on. 345 figsize: Optional tuple width, height in inches. 346 name: Name of seasonality component if changed from default 'weekly'. 347 348 Returns 349 ------- 350 a list of matplotlib artists 351 """ 352 artists = [] 353 if not ax: 354 fig = plt.figure(facecolor='w', figsize=figsize) 355 ax = fig.add_subplot(111) 356 # Compute weekly seasonality for a Sun-Sat sequence of dates. 357 days = (pd.date_range(start='2017-01-01', periods=7) + 358 pd.Timedelta(days=weekly_start)) 359 df_w = seasonality_plot_df(m, days) 360 seas = m.predict_seasonal_components(df_w) 361 days = days.day_name() 362 artists += ax.plot(range(len(days)), seas[name], ls='-', 363 c='#0072B2') 364 if uncertainty and m.uncertainty_samples: 365 artists += [ax.fill_between(range(len(days)), 366 seas[name + '_lower'], seas[name + '_upper'], 367 color='#0072B2', alpha=0.2)] 368 ax.grid(True, which='major', c='gray', ls='-', lw=1, alpha=0.2) 369 ax.set_xticks(range(len(days))) 370 ax.set_xticklabels(days) 371 ax.set_xlabel('Day of week') 372 ax.set_ylabel(name) 373 if m.seasonalities[name]['mode'] == 'multiplicative': 374 ax = set_y_as_percent(ax) 375 return artists
Plot the weekly component of the forecast.
Parameters
m (Prophet model.):
ax (Optional matplotlib Axes to plot on. One will be created if this): is not provided.
- uncertainty (Optional boolean to plot uncertainty intervals, which will): only be done if m.uncertainty_samples > 0.
- weekly_start (Optional 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.
figsize (Optional tuple width, height in inches.):
name (Name of seasonality component if changed from default 'weekly'.):
Returns
- a list of matplotlib artists
378def plot_yearly( 379 m: Prophet, 380 ax: plt.Axes | None = None, 381 uncertainty: bool = True, 382 yearly_start: int = 0, 383 figsize: tuple[int, int] = (10, 6), 384 name: str = 'yearly', 385) -> Sequence[plt.Artist]: 386 """Plot the yearly component of the forecast. 387 388 Parameters 389 ---------- 390 m: Prophet model. 391 ax: Optional matplotlib Axes to plot on. One will be created if 392 this is not provided. 393 uncertainty: Optional boolean to plot uncertainty intervals, which will 394 only be done if m.uncertainty_samples > 0. 395 yearly_start: Optional int specifying the start day of the yearly 396 seasonality plot. 0 (default) starts the year on Jan 1. 1 shifts 397 by 1 day to Jan 2, and so on. 398 figsize: Optional tuple width, height in inches. 399 name: Name of seasonality component if previously changed from default 'yearly'. 400 401 Returns 402 ------- 403 a list of matplotlib artists 404 """ 405 artists = [] 406 if not ax: 407 fig = plt.figure(facecolor='w', figsize=figsize) 408 ax = fig.add_subplot(111) 409 # Compute yearly seasonality for a Jan 1 - Dec 31 sequence of dates. 410 days = (pd.date_range(start='2017-01-01', periods=365) + 411 pd.Timedelta(days=yearly_start)) 412 df_y = seasonality_plot_df(m, days) 413 seas = m.predict_seasonal_components(df_y) 414 artists += ax.plot( 415 df_y['ds'], seas[name], ls='-', c='#0072B2') 416 if uncertainty and m.uncertainty_samples: 417 artists += [ax.fill_between( 418 df_y['ds'], seas[name + '_lower'], 419 seas[name + '_upper'], color='#0072B2', alpha=0.2)] 420 ax.grid(True, which='major', c='gray', ls='-', lw=1, alpha=0.2) 421 months = MonthLocator(range(1, 13), bymonthday=1, interval=2) 422 ax.xaxis.set_major_formatter(FuncFormatter( 423 lambda x, pos=None: '{dt:%B} {dt.day}'.format(dt=num2date(x)))) 424 ax.xaxis.set_major_locator(months) 425 ax.set_xlabel('Day of year') 426 ax.set_ylabel(name) 427 if m.seasonalities[name]['mode'] == 'multiplicative': 428 ax = set_y_as_percent(ax) 429 return artists
Plot the yearly component of the forecast.
Parameters
m (Prophet model.):
ax (Optional matplotlib Axes to plot on. One will be created if): this is not provided.
- uncertainty (Optional boolean to plot uncertainty intervals, which will): only be done if m.uncertainty_samples > 0.
- yearly_start (Optional 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 (Optional tuple width, height in inches.):
name (Name of seasonality component if previously changed from default 'yearly'.):
Returns
- a list of matplotlib artists
432def plot_seasonality( 433 m: Prophet, 434 name: str, 435 ax: plt.Axes | None = None, 436 uncertainty: bool = True, 437 figsize: tuple[int, int] = (10, 6), 438) -> Sequence[plt.Artist]: 439 """Plot a custom seasonal component. 440 441 Parameters 442 ---------- 443 m: Prophet model. 444 name: Seasonality name, like 'daily', 'weekly'. 445 ax: Optional matplotlib Axes to plot on. One will be created if 446 this is not provided. 447 uncertainty: Optional boolean to plot uncertainty intervals, which will 448 only be done if m.uncertainty_samples > 0. 449 figsize: Optional tuple width, height in inches. 450 451 Returns 452 ------- 453 a list of matplotlib artists 454 """ 455 artists = [] 456 if not ax: 457 fig = plt.figure(facecolor='w', figsize=figsize) 458 ax = fig.add_subplot(111) 459 # Compute seasonality from Jan 1 through a single period. 460 start = pd.to_datetime('2017-01-01 0000') 461 period = m.seasonalities[name]['period'] 462 end = start + pd.Timedelta(days=period) 463 plot_points = 200 464 # https://github.com/pandas-dev/pandas-stubs/issues/1645 465 days = pd.to_datetime(np.linspace(start.value, end.value, plot_points)) # pyrefly:ignore[no-matching-overload] 466 df_y = seasonality_plot_df(m, days) 467 seas = m.predict_seasonal_components(df_y) 468 artists += ax.plot(df_y['ds'], seas[name], ls='-', 469 c='#0072B2') 470 if uncertainty and m.uncertainty_samples: 471 artists += [ax.fill_between( 472 df_y['ds'], seas[name + '_lower'], 473 seas[name + '_upper'], color='#0072B2', alpha=0.2)] 474 ax.grid(True, which='major', c='gray', ls='-', lw=1, alpha=0.2) 475 n_ticks = 8 476 # https://github.com/pandas-dev/pandas-stubs/issues/1645 477 xticks = pd.to_datetime(np.linspace(start.value, end.value, n_ticks) # pyrefly:ignore[no-matching-overload] 478 ).to_pydatetime() 479 ax.set_xticks(xticks) 480 if name == 'yearly': 481 fmt = FuncFormatter( 482 lambda x, pos=None: '{dt:%B} {dt.day}'.format(dt=num2date(x))) 483 ax.set_xlabel('Day of year') 484 elif name == 'weekly': 485 fmt = FuncFormatter( 486 lambda x, pos=None: '{dt:%A}'.format(dt=num2date(x))) 487 ax.set_xlabel('Day of Week') 488 elif name == 'daily': 489 fmt = FuncFormatter( 490 lambda x, pos=None: '{dt:%T}'.format(dt=num2date(x))) 491 ax.set_xlabel('Hour of day') 492 elif period <= 2: 493 fmt = FuncFormatter( 494 lambda x, pos=None: '{dt:%T}'.format(dt=num2date(x))) 495 ax.set_xlabel('Hours') 496 else: 497 fmt = FuncFormatter( 498 lambda x, pos=None: '{:.0f}'.format(pos * period / (n_ticks - 1))) 499 ax.set_xlabel('Days') 500 ax.xaxis.set_major_formatter(fmt) 501 ax.set_ylabel(name) 502 if m.seasonalities[name]['mode'] == 'multiplicative': 503 ax = set_y_as_percent(ax) 504 return artists
Plot a custom seasonal component.
Parameters
m (Prophet model.):
name (Seasonality name, like 'daily', 'weekly'.):
ax (Optional matplotlib Axes to plot on. One will be created if): this is not provided.
- uncertainty (Optional boolean to plot uncertainty intervals, which will): only be done if m.uncertainty_samples > 0.
- figsize (Optional tuple width, height in inches.):
Returns
- a list of matplotlib artists
515def add_changepoints_to_plot( 516 ax: plt.Axes, 517 m: Prophet, 518 fcst: pd.DataFrame, 519 threshold: float = 0.01, 520 cp_color: str = 'r', 521 cp_linestyle: str = '--', 522 trend: bool = True, 523) -> list[plt.Line2D]: 524 """Add markers for significant changepoints to prophet forecast plot. 525 526 Example: 527 fig = m.plot(forecast) 528 add_changepoints_to_plot(fig.gca(), m, forecast) 529 530 Parameters 531 ---------- 532 ax: axis on which to overlay changepoint markers. 533 m: Prophet model. 534 fcst: Forecast output from m.predict. 535 threshold: Threshold on trend change magnitude for significance. 536 cp_color: Color of changepoint markers. 537 cp_linestyle: Linestyle for changepoint markers. 538 trend: If True, will also overlay the trend. 539 540 Returns 541 ------- 542 a list of matplotlib artists 543 """ 544 artists = [] 545 if trend: 546 artists.extend(ax.plot(fcst['ds'], fcst['trend'], c=cp_color)) 547 548 assert m.changepoints is not None 549 signif_changepoints = m.changepoints[ 550 np.abs(np.nanmean(m.params['delta'], axis=0)) >= threshold 551 ] if len(m.changepoints) > 0 else [] 552 for cp in signif_changepoints: 553 # Matplotlib stubs type axvline x as float; pandas Timestamp is accepted at runtime. 554 artists.append(ax.axvline(x=cp, c=cp_color, ls=cp_linestyle)) # pyrefly:ignore[bad-argument-type] 555 return artists
Add markers for significant changepoints to prophet forecast plot.
Example: fig = m.plot(forecast) add_changepoints_to_plot(fig.gca(), m, forecast)
Parameters
ax (axis on which to overlay changepoint markers.):
m (Prophet model.):
fcst (Forecast output from m.predict.):
threshold (Threshold on trend change magnitude for significance.):
cp_color (Color of changepoint markers.):
cp_linestyle (Linestyle for changepoint markers.):
trend (If True, will also overlay the trend.):
Returns
- a list of matplotlib artists
558def plot_cross_validation_metric( 559 df_cv: pd.DataFrame, 560 metric: str, 561 rolling_window: float = 0.1, 562 ax: plt.Axes | None = None, 563 figsize: tuple[int, int] = (10, 6), 564 color: str = 'b', 565 point_color: str = 'gray', 566) -> plt.Figure: 567 """Plot a performance metric vs. forecast horizon from cross validation. 568 569 Cross validation produces a collection of out-of-sample model predictions 570 that can be compared to actual values, at a range of different horizons 571 (distance from the cutoff). This computes a specified performance metric 572 for each prediction, and aggregated over a rolling window with horizon. 573 574 This uses prophet.diagnostics.performance_metrics to compute the metrics. 575 Valid values of metric are 'mse', 'rmse', 'mae', 'mape', 'mdape', 'smape', and 'coverage'. 576 577 rolling_window is the proportion of data included in the rolling window of 578 aggregation. The default value of 0.1 means 10% of data are included in the 579 aggregation for computing the metric. 580 581 As a concrete example, if metric='mse', then this plot will show the 582 squared error for each cross validation prediction, along with the MSE 583 averaged over rolling windows of 10% of the data. 584 585 Parameters 586 ---------- 587 df_cv: The output from prophet.diagnostics.cross_validation. 588 metric: Metric name, one of ['mse', 'rmse', 'mae', 'mape', 'mdape', 'smape', 'coverage']. 589 rolling_window: Proportion of data to use for rolling average of metric. 590 In [0, 1]. Defaults to 0.1. 591 ax: Optional matplotlib axis on which to plot. If not given, a new figure 592 will be created. 593 figsize: Optional tuple width, height in inches. 594 color: Optional color for plot and error points, useful when plotting 595 multiple model performances on one axis for comparison. 596 597 Returns 598 ------- 599 a matplotlib figure. 600 """ 601 if ax is None: 602 fig = plt.figure(facecolor='w', figsize=figsize) 603 ax = fig.add_subplot(111) 604 else: 605 fig = cast('plt.Figure', ax.get_figure()) 606 # Get the metric at the level of individual predictions, and with the rolling window. 607 df_none = performance_metrics(df_cv, metrics=[metric], rolling_window=-1) 608 df_h = performance_metrics(df_cv, metrics=[metric], rolling_window=rolling_window) 609 610 assert df_none is not None 611 assert df_h is not None 612 613 # Some work because matplotlib does not handle timedelta 614 # Target ~10 ticks. 615 tick_w = max(df_none['horizon'].astype('timedelta64[ns]')) / 10. 616 # Find the largest time resolution that has <1 unit per bin. 617 dts: list[Literal["D", "h", "m", "s", "ms", "us", "ns"]] 618 dts = ['D', 'h', 'm', 's', 'ms', 'us', 'ns'] 619 dt_names = [ 620 'days', 'hours', 'minutes', 'seconds', 'milliseconds', 'microseconds', 621 'nanoseconds' 622 ] 623 dt_conversions = [ 624 24 * 60 * 60 * 10 ** 9, 625 60 * 60 * 10 ** 9, 626 60 * 10 ** 9, 627 10 ** 9, 628 10 ** 6, 629 10 ** 3, 630 1., 631 ] 632 for i, dt in enumerate(dts): 633 if np.timedelta64(1, dt) < np.timedelta64(tick_w, 'ns'): 634 break 635 636 x_plt = np.asarray(df_none['horizon'].astype('timedelta64[ns]')).view(np.int64) / float(dt_conversions[i]) 637 x_plt_h = np.asarray(df_h['horizon'].astype('timedelta64[ns]')).view(np.int64) / float(dt_conversions[i]) 638 639 ax.plot(x_plt, df_none[metric], '.', alpha=0.1, c=point_color) 640 ax.plot(x_plt_h, df_h[metric], '-', c=color) 641 ax.grid(True) 642 643 ax.set_xlabel('Horizon ({})'.format(dt_names[i])) 644 ax.set_ylabel(metric) 645 return fig
Plot a performance metric vs. forecast horizon from cross validation.
Cross validation produces a collection of out-of-sample model predictions that can be compared to actual values, at a range of different horizons (distance from the cutoff). This computes a specified performance metric for each prediction, and aggregated over a rolling window with horizon.
This uses prophet.diagnostics.performance_metrics to compute the metrics. Valid values of metric are 'mse', 'rmse', 'mae', 'mape', 'mdape', 'smape', and 'coverage'.
rolling_window is the proportion of data included in the rolling window of aggregation. The default value of 0.1 means 10% of data are included in the aggregation for computing the metric.
As a concrete example, if metric='mse', then this plot will show the squared error for each cross validation prediction, along with the MSE averaged over rolling windows of 10% of the data.
Parameters
df_cv (The output from prophet.diagnostics.cross_validation.):
metric (Metric name, one of ['mse', 'rmse', 'mae', 'mape', 'mdape', 'smape', 'coverage'].):
rolling_window (Proportion of data to use for rolling average of metric.): In [0, 1]. Defaults to 0.1.
- ax (Optional matplotlib axis on which to plot. If not given, a new figure): will be created.
figsize (Optional tuple width, height in inches.):
color (Optional color for plot and error points, useful when plotting): multiple model performances on one axis for comparison.
Returns
- a matplotlib figure.
648def plot_plotly( 649 m: Prophet, 650 fcst: pd.DataFrame, 651 uncertainty: bool = True, 652 plot_cap: bool = True, 653 trend: bool = False, 654 changepoints: bool = False, 655 changepoints_threshold: float = 0.01, 656 xlabel: str = 'ds', 657 ylabel: str = 'y', 658 figsize: tuple[int, int] = (900, 600) 659) -> go.Figure: 660 """Plot the Prophet forecast with Plotly offline. 661 662 Plotting in Jupyter Notebook requires initializing plotly.offline.init_notebook_mode(): 663 >>> import plotly.offline as py 664 >>> py.init_notebook_mode() 665 Then the figure can be displayed using plotly.offline.iplot(...): 666 >>> fig = plot_plotly(m, fcst) 667 >>> py.iplot(fig) 668 see https://plot.ly/python/offline/ for details 669 670 Parameters 671 ---------- 672 m: Prophet model. 673 fcst: pd.DataFrame output of m.predict. 674 uncertainty: Optional boolean to plot uncertainty intervals. 675 plot_cap: Optional boolean indicating if the capacity should be shown 676 in the figure, if available. 677 trend: Optional boolean to plot trend 678 changepoints: Optional boolean to plot changepoints 679 changepoints_threshold: Threshold on trend change magnitude for significance. 680 xlabel: Optional label name on X-axis 681 ylabel: Optional label name on Y-axis 682 figsize: The plot's size (in px). 683 684 Returns 685 ------- 686 A Plotly Figure. 687 """ 688 prediction_color = '#0072B2' 689 error_color = 'rgba(0, 114, 178, 0.2)' # '#0072B2' with 0.2 opacity 690 actual_color = 'black' 691 cap_color = 'black' 692 trend_color = '#B23B00' 693 line_width = 2 694 marker_size = 4 695 696 data = [] 697 # Add actual 698 assert m.history 699 data.append(go.Scatter( 700 name='Actual', 701 x=m.history['ds'], 702 y=m.history['y'], 703 marker=dict(color=actual_color, size=marker_size), 704 mode='markers' 705 )) 706 # Add lower bound 707 if uncertainty and m.uncertainty_samples: 708 data.append(go.Scatter( 709 x=fcst['ds'], 710 y=fcst['yhat_lower'], 711 mode='lines', 712 line=dict(width=0), 713 hoverinfo='skip' 714 )) 715 # Add prediction 716 data.append(go.Scatter( 717 name='Predicted', 718 x=fcst['ds'], 719 y=fcst['yhat'], 720 mode='lines', 721 line=dict(color=prediction_color, width=line_width), 722 fillcolor=error_color, 723 fill='tonexty' if uncertainty and m.uncertainty_samples else 'none' 724 )) 725 # Add upper bound 726 if uncertainty and m.uncertainty_samples: 727 data.append(go.Scatter( 728 x=fcst['ds'], 729 y=fcst['yhat_upper'], 730 mode='lines', 731 line=dict(width=0), 732 fillcolor=error_color, 733 fill='tonexty', 734 hoverinfo='skip' 735 )) 736 # Add caps 737 if 'cap' in fcst and plot_cap: 738 data.append(go.Scatter( 739 name='Cap', 740 x=fcst['ds'], 741 y=fcst['cap'], 742 mode='lines', 743 line=dict(color=cap_color, dash='dash', width=line_width), 744 )) 745 if m.logistic_floor and 'floor' in fcst and plot_cap: 746 data.append(go.Scatter( 747 name='Floor', 748 x=fcst['ds'], 749 y=fcst['floor'], 750 mode='lines', 751 line=dict(color=cap_color, dash='dash', width=line_width), 752 )) 753 # Add trend 754 if trend: 755 data.append(go.Scatter( 756 name='Trend', 757 x=fcst['ds'], 758 y=fcst['trend'], 759 mode='lines', 760 line=dict(color=trend_color, width=line_width), 761 )) 762 # Add changepoints 763 assert m.changepoints 764 if changepoints and len(m.changepoints) > 0: 765 signif_changepoints = m.changepoints[ 766 np.abs(np.nanmean(m.params['delta'], axis=0)) >= changepoints_threshold 767 ] 768 data.append(go.Scatter( 769 x=signif_changepoints, 770 y=fcst.loc[fcst['ds'].isin(signif_changepoints), 'trend'], 771 marker=dict(size=50, symbol='line-ns-open', color=trend_color, 772 line=dict(width=line_width)), 773 mode='markers', 774 hoverinfo='skip' 775 )) 776 777 layout = dict( 778 showlegend=False, 779 width=figsize[0], 780 height=figsize[1], 781 yaxis=dict( 782 title=ylabel 783 ), 784 xaxis=dict( 785 title=xlabel, 786 type='date', 787 rangeselector=dict( 788 buttons=list([ 789 dict(count=7, 790 label='1w', 791 step='day', 792 stepmode='backward'), 793 dict(count=1, 794 label='1m', 795 step='month', 796 stepmode='backward'), 797 dict(count=6, 798 label='6m', 799 step='month', 800 stepmode='backward'), 801 dict(count=1, 802 label='1y', 803 step='year', 804 stepmode='backward'), 805 dict(step='all') 806 ]) 807 ), 808 rangeslider=dict( 809 visible=True 810 ), 811 ), 812 ) 813 fig = go.Figure(data=data, layout=layout) 814 return fig
Plot the Prophet forecast with Plotly offline.
Plotting in Jupyter Notebook requires initializing plotly.offline.init_notebook_mode():
>>> import plotly.offline as py
>>> py.init_notebook_mode()
Then the figure can be displayed using plotly.offline.iplot(...):
>>> fig = plot_plotly(m, fcst)
>>> py.iplot(fig)
see https://plot.ly/python/offline/ for details
Parameters
m (Prophet model.):
fcst (pd.DataFrame output of m.predict.):
uncertainty (Optional boolean to plot uncertainty intervals.):
plot_cap (Optional boolean indicating if the capacity should be shown): in the figure, if available.
trend (Optional boolean to plot trend):
changepoints (Optional boolean to plot changepoints):
changepoints_threshold (Threshold on trend change magnitude for significance.):
xlabel (Optional label name on X-axis):
ylabel (Optional label name on Y-axis):
figsize (The plot's size (in px).):
Returns
- A Plotly Figure.
817def plot_components_plotly( 818 m: Prophet, 819 fcst: pd.DataFrame, 820 uncertainty: bool = True, 821 plot_cap: bool = True, 822 figsize: tuple[int, int] = (900, 200), 823) -> go.Figure: 824 """Plot the Prophet forecast components using Plotly. 825 See plot_plotly() for Plotly setup instructions 826 827 Will plot whichever are available of: trend, holidays, weekly 828 seasonality, yearly seasonality, and additive and multiplicative extra 829 regressors. 830 831 Parameters 832 ---------- 833 m: Prophet model. 834 fcst: pd.DataFrame output of m.predict. 835 uncertainty: Optional boolean to plot uncertainty intervals, which will 836 only be done if m.uncertainty_samples > 0. 837 plot_cap: Optional boolean indicating if the capacity should be shown 838 in the figure, if available. 839 figsize: Set the size for the subplots (in px). 840 841 Returns 842 ------- 843 A Plotly Figure. 844 """ 845 846 # Identify components to plot and get their Plotly props 847 components = {} 848 components['trend'] = get_forecast_component_plotly_props( 849 m, fcst, 'trend', uncertainty, plot_cap) 850 if m.train_holiday_names is not None and 'holidays' in fcst: 851 components['holidays'] = get_forecast_component_plotly_props( 852 m, fcst, 'holidays', uncertainty) 853 854 regressors = {'additive': False, 'multiplicative': False} 855 for name, props in m.extra_regressors.items(): 856 regressors[props['mode']] = True 857 for mode in ['additive', 'multiplicative']: 858 if regressors[mode] and 'extra_regressors_{}'.format(mode) in fcst: 859 components['extra_regressors_{}'.format(mode)] = get_forecast_component_plotly_props( 860 m, fcst, 'extra_regressors_{}'.format(mode)) 861 for seasonality in m.seasonalities: 862 components[seasonality] = get_seasonality_plotly_props(m, seasonality) 863 864 # Create Plotly subplot figure and add the components to it 865 fig = make_subplots(rows=len(components), cols=1, print_grid=False) 866 fig['layout'].update(go.Layout( 867 showlegend=False, 868 width=figsize[0], 869 height=figsize[1] * len(components) 870 )) 871 for i, name in enumerate(components): 872 if i == 0: 873 xaxis = fig['layout']['xaxis'] 874 yaxis = fig['layout']['yaxis'] 875 else: 876 xaxis = fig['layout']['xaxis{}'.format(i + 1)] 877 yaxis = fig['layout']['yaxis{}'.format(i + 1)] 878 xaxis.update(components[name]['xaxis']) 879 yaxis.update(components[name]['yaxis']) 880 for trace in components[name]['traces']: 881 fig.append_trace(trace, i + 1, 1) 882 return fig
Plot the Prophet forecast components using Plotly. See plot_plotly() for Plotly setup instructions
Will plot whichever are available of: trend, holidays, weekly seasonality, yearly seasonality, and additive and multiplicative extra regressors.
Parameters
m (Prophet model.):
fcst (pd.DataFrame output of m.predict.):
uncertainty (Optional boolean to plot uncertainty intervals, which will): only be done if m.uncertainty_samples > 0.
- plot_cap (Optional boolean indicating if the capacity should be shown): in the figure, if available.
- figsize (Set the size for the subplots (in px).):
Returns
- A Plotly Figure.
885def plot_forecast_component_plotly( 886 m: Prophet, 887 fcst: pd.DataFrame, 888 name: str, 889 uncertainty: bool = True, 890 plot_cap: bool = False, 891 figsize: tuple[int, int] = (900, 300) 892) -> go.Figure: 893 """Plot a particular component of the forecast using Plotly. 894 See plot_plotly() for Plotly setup instructions 895 896 Parameters 897 ---------- 898 m: Prophet model. 899 fcst: pd.DataFrame output of m.predict. 900 name: Name of the component to plot. 901 uncertainty: Optional boolean to plot uncertainty intervals, which will 902 only be done if m.uncertainty_samples > 0. 903 plot_cap: Optional boolean indicating if the capacity should be shown 904 in the figure, if available. 905 figsize: The plot's size (in px). 906 907 Returns 908 ------- 909 A Plotly Figure. 910 """ 911 props = get_forecast_component_plotly_props(m, fcst, name, uncertainty, plot_cap) 912 layout = go.Layout( 913 width=figsize[0], 914 height=figsize[1], 915 showlegend=False, 916 xaxis=props['xaxis'], 917 yaxis=props['yaxis'] 918 ) 919 fig = go.Figure(data=props['traces'], layout=layout) 920 return fig
Plot a particular component of the forecast using Plotly. See plot_plotly() for Plotly setup instructions
Parameters
m (Prophet model.):
fcst (pd.DataFrame output of m.predict.):
name (Name of the component to plot.):
uncertainty (Optional boolean to plot uncertainty intervals, which will): only be done if m.uncertainty_samples > 0.
- plot_cap (Optional boolean indicating if the capacity should be shown): in the figure, if available.
- figsize (The plot's size (in px).):
Returns
- A Plotly Figure.
923def plot_seasonality_plotly( 924 m: Prophet, 925 name: str, 926 uncertainty: bool = True, 927 figsize: tuple[int, int] = (900, 300) 928) -> go.Figure: 929 """Plot a custom seasonal component using Plotly. 930 See plot_plotly() for Plotly setup instructions 931 932 Parameters 933 ---------- 934 m: Prophet model. 935 name: Seasonality name, like 'daily', 'weekly'. 936 uncertainty: Optional boolean to plot uncertainty intervals, which will 937 only be done if m.uncertainty_samples > 0. 938 figsize: Set the plot's size (in px). 939 940 Returns 941 ------- 942 A Plotly Figure. 943 """ 944 props = get_seasonality_plotly_props(m, name, uncertainty) 945 layout = go.Layout( 946 width=figsize[0], 947 height=figsize[1], 948 showlegend=False, 949 xaxis=props['xaxis'], 950 yaxis=props['yaxis'] 951 ) 952 fig = go.Figure(data=props['traces'], layout=layout) 953 return fig
Plot a custom seasonal component using Plotly. See plot_plotly() for Plotly setup instructions
Parameters
m (Prophet model.):
name (Seasonality name, like 'daily', 'weekly'.):
uncertainty (Optional boolean to plot uncertainty intervals, which will): only be done if m.uncertainty_samples > 0.
- figsize (Set the plot's size (in px).):
Returns
- A Plotly Figure.
956def get_forecast_component_plotly_props( 957 m: Prophet, 958 fcst: pd.DataFrame, 959 name: str, 960 uncertainty: bool = True, 961 plot_cap: bool = False, 962) -> _PlotlyProps: 963 """Prepares a dictionary for plotting the selected forecast component with Plotly 964 965 Parameters 966 ---------- 967 m: Prophet model. 968 fcst: pd.DataFrame output of m.predict. 969 name: Name of the component to plot. 970 uncertainty: Optional boolean to plot uncertainty intervals, which will 971 only be done if m.uncertainty_samples > 0. 972 plot_cap: Optional boolean indicating if the capacity should be shown 973 in the figure, if available. 974 975 Returns 976 ------- 977 A dictionary with Plotly traces, xaxis and yaxis 978 """ 979 prediction_color = '#0072B2' 980 error_color = 'rgba(0, 114, 178, 0.2)' # '#0072B2' with 0.2 opacity 981 cap_color = 'black' 982 zeroline_color = '#AAA' 983 line_width = 2 984 985 range_margin = (fcst['ds'].max() - fcst['ds'].min()) * 0.05 986 range_x = [fcst['ds'].min() - range_margin, fcst['ds'].max() + range_margin] 987 988 text = None 989 mode = 'lines' 990 if name == 'holidays': 991 992 # Combine holidays into one hover text 993 holidays = m.construct_holiday_dataframe(fcst['ds']) 994 holiday_features, _, _ = m.make_holiday_features(fcst['ds'], holidays) 995 holiday_features.columns = holiday_features.columns.str.replace('_delim_', '', regex=False) 996 holiday_features.columns = holiday_features.columns.str.replace('+0', '', regex=False) 997 text = pd.Series(data='', index=holiday_features.index) 998 for holiday_feature, idxs in holiday_features.items(): 999 # https://github.com/facebook/pyrefly/issues/2248 1000 # pyrefly:ignore[unsupported-operation] 1001 text[idxs.astype(bool) & (text != '')] += '<br>' # Add newline if additional holiday 1002 text[idxs.astype(bool)] += holiday_feature # pyrefly:ignore[unsupported-operation] 1003 1004 traces = [] 1005 traces.append(go.Scatter( 1006 name=name, 1007 x=fcst['ds'], 1008 y=fcst[name], 1009 mode=mode, 1010 line=go.scatter.Line(color=prediction_color, width=line_width), 1011 text=text, 1012 )) 1013 if uncertainty and m.uncertainty_samples and (fcst[name + '_upper'] != fcst[name + '_lower']).any(): 1014 if mode == 'markers': 1015 traces[0].update( 1016 error_y=dict( 1017 type='data', 1018 symmetric=False, 1019 array=fcst[name + '_upper'], 1020 arrayminus=fcst[name + '_lower'], 1021 width=0, 1022 color=error_color 1023 ) 1024 ) 1025 else: 1026 traces.append(go.Scatter( 1027 name=name + '_upper', 1028 x=fcst['ds'], 1029 y=fcst[name + '_upper'], 1030 mode=mode, 1031 line=go.scatter.Line(width=0, color=error_color) 1032 )) 1033 traces.append(go.Scatter( 1034 name=name + '_lower', 1035 x=fcst['ds'], 1036 y=fcst[name + '_lower'], 1037 mode=mode, 1038 line=go.scatter.Line(width=0, color=error_color), 1039 fillcolor=error_color, 1040 fill='tonexty' 1041 )) 1042 if 'cap' in fcst and plot_cap: 1043 traces.append(go.Scatter( 1044 name='Cap', 1045 x=fcst['ds'], 1046 y=fcst['cap'], 1047 mode='lines', 1048 line=go.scatter.Line(color=cap_color, dash='dash', width=line_width), 1049 )) 1050 if m.logistic_floor and 'floor' in fcst and plot_cap: 1051 traces.append(go.Scatter( 1052 name='Floor', 1053 x=fcst['ds'], 1054 y=fcst['floor'], 1055 mode='lines', 1056 line=go.scatter.Line(color=cap_color, dash='dash', width=line_width), 1057 )) 1058 1059 xaxis = go.layout.XAxis( 1060 type='date', 1061 range=range_x) 1062 yaxis = go.layout.YAxis(rangemode='normal' if name == 'trend' else 'tozero', 1063 title=go.layout.yaxis.Title(text=name), 1064 zerolinecolor=zeroline_color) 1065 assert m.component_modes 1066 if name in m.component_modes['multiplicative']: 1067 yaxis.update(tickformat='%', hoverformat='.2%') 1068 return {'traces': traces, 'xaxis': xaxis, 'yaxis': yaxis}
Prepares a dictionary for plotting the selected forecast component with Plotly
Parameters
m (Prophet model.):
fcst (pd.DataFrame output of m.predict.):
name (Name of the component to plot.):
uncertainty (Optional boolean to plot uncertainty intervals, which will): only be done if m.uncertainty_samples > 0.
- plot_cap (Optional boolean indicating if the capacity should be shown): in the figure, if available.
Returns
- A dictionary with Plotly traces, xaxis and yaxis
1071def get_seasonality_plotly_props( 1072 m: Prophet, 1073 name: str, 1074 uncertainty: bool = True, 1075) -> _PlotlyProps: 1076 """Prepares a dictionary for plotting the selected seasonality with Plotly 1077 1078 Parameters 1079 ---------- 1080 m: Prophet model. 1081 name: Name of the component to plot. 1082 uncertainty: Optional boolean to plot uncertainty intervals, which will 1083 only be done if m.uncertainty_samples > 0. 1084 1085 Returns 1086 ------- 1087 A dictionary with Plotly traces, xaxis and yaxis 1088 """ 1089 prediction_color = '#0072B2' 1090 error_color = 'rgba(0, 114, 178, 0.2)' # '#0072B2' with 0.2 opacity 1091 line_width = 2 1092 zeroline_color = '#AAA' 1093 1094 # Compute seasonality from Jan 1 through a single period. 1095 start = pd.to_datetime('2017-01-01 0000') 1096 period = m.seasonalities[name]['period'] 1097 end = start + pd.Timedelta(days=period) 1098 assert m.history is not None 1099 if (m.history['ds'].dt.hour == 0).all(): # Day Precision 1100 plot_points = np.floor(period).astype(int) 1101 elif (m.history['ds'].dt.minute == 0).all(): # Hour Precision 1102 plot_points = np.floor(period * 24).astype(int) 1103 else: # Minute Precision 1104 plot_points = np.floor(period * 24 * 60).astype(int) 1105 days = pd.to_datetime(np.linspace(start.value, end.value, plot_points, endpoint=False)) 1106 df_y = seasonality_plot_df(m, days) 1107 seas = m.predict_seasonal_components(df_y) 1108 1109 traces = [] 1110 traces.append(go.Scatter( 1111 name=name, 1112 x=df_y['ds'], 1113 y=seas[name], 1114 mode='lines', 1115 line=go.scatter.Line(color=prediction_color, width=line_width) 1116 )) 1117 if uncertainty and m.uncertainty_samples and (seas[name + '_upper'] != seas[name + '_lower']).any(): 1118 traces.append(go.Scatter( 1119 name=name + '_upper', 1120 x=df_y['ds'], 1121 y=seas[name + '_upper'], 1122 mode='lines', 1123 line=go.scatter.Line(width=0, color=error_color) 1124 )) 1125 traces.append(go.Scatter( 1126 name=name + '_lower', 1127 x=df_y['ds'], 1128 y=seas[name + '_lower'], 1129 mode='lines', 1130 line=go.scatter.Line(width=0, color=error_color), 1131 fillcolor=error_color, 1132 fill='tonexty' 1133 )) 1134 1135 # Set tick formats (examples are based on 2017-01-06 21:15) 1136 if period <= 2: 1137 tickformat = '%H:%M' # "21:15" 1138 elif period < 7: 1139 tickformat = '%A %H:%M' # "Friday 21:15" 1140 elif period < 14: 1141 tickformat = '%A' # "Friday" 1142 else: 1143 tickformat = '%B %e' # "January 6" 1144 1145 range_margin = (df_y['ds'].max() - df_y['ds'].min()) * 0.05 1146 xaxis = go.layout.XAxis( 1147 tickformat=tickformat, 1148 type='date', 1149 range=[df_y['ds'].min() - range_margin, df_y['ds'].max() + range_margin] 1150 ) 1151 1152 yaxis = go.layout.YAxis(title=go.layout.yaxis.Title(text=name), 1153 zerolinecolor=zeroline_color) 1154 if m.seasonalities[name]['mode'] == 'multiplicative': 1155 yaxis.update(tickformat='%', hoverformat='.2%') 1156 1157 return {'traces': traces, 'xaxis': xaxis, 'yaxis': yaxis}
Prepares a dictionary for plotting the selected seasonality with Plotly
Parameters
m (Prophet model.):
name (Name of the component to plot.):
uncertainty (Optional boolean to plot uncertainty intervals, which will): only be done if m.uncertainty_samples > 0.
Returns
- A dictionary with Plotly traces, xaxis and yaxis