mgplot

mgplot

Package to provide a frontend to matplotlib for working with timeseries data that is indexed with a PeriodIndex.

  1"""
  2mgplot
  3------
  4
  5Package to provide a frontend to matplotlib for working
  6with timeseries data that is indexed with a PeriodIndex.
  7"""
  8
  9# --- version and author
 10import importlib.metadata
 11
 12# --- local imports
 13#    Do not import the utilities, test nor type-checking modules here.
 14from mgplot.finalise_plot import finalise_plot, FINALISE_KW_TYPES
 15from mgplot.bar_plot import bar_plot, BAR_KW_TYPES
 16from mgplot.line_plot import line_plot, LINE_KW_TYPES
 17from mgplot.seastrend_plot import seastrend_plot, SEASTREND_KW_TYPES
 18from mgplot.postcovid_plot import postcovid_plot, POSTCOVID_KW_TYPES
 19from mgplot.revision_plot import revision_plot, REVISION_KW_TYPES
 20from mgplot.run_plot import run_plot, RUN_KW_TYPES
 21from mgplot.summary_plot import summary_plot, SUMMARY_KW_TYPES
 22from mgplot.growth_plot import (
 23    calc_growth,
 24    growth_plot,
 25    series_growth_plot,
 26    SERIES_GROWTH_KW_TYPES,
 27    GROWTH_KW_TYPES,
 28)
 29from mgplot.multi_plot import (
 30    multi_start,
 31    multi_column,
 32    plot_then_finalise,
 33)
 34from mgplot.colors import (
 35    get_color,
 36    get_party_palette,
 37    colorise_list,
 38    contrast,
 39    abbreviate_state,
 40    state_names,
 41    state_abbrs,
 42)
 43from mgplot.settings import (
 44    get_setting,
 45    set_setting,
 46    set_chart_dir,
 47    clear_chart_dir,
 48)
 49from mgplot.finalisers import (
 50    line_plot_finalise,
 51    bar_plot_finalise,
 52    seastrend_plot_finalise,
 53    postcovid_plot_finalise,
 54    revision_plot_finalise,
 55    summary_plot_finalise,
 56    growth_plot_finalise,
 57    series_growth_plot_finalise,
 58    run_plot_finalise,
 59)
 60
 61
 62# --- version and author
 63try:
 64    __version__ = importlib.metadata.version(__name__)
 65except importlib.metadata.PackageNotFoundError:
 66    __version__ = "0.0.0"  # Fallback for development mode
 67__author__ = "Bryan Palmer"
 68
 69
 70# --- public API
 71__all__ = (
 72    "__version__",
 73    "__author__",
 74    # --- settings
 75    "get_setting",
 76    "set_setting",
 77    "set_chart_dir",
 78    "clear_chart_dir",
 79    # --- colors
 80    "get_color",
 81    "get_party_palette",
 82    "colorise_list",
 83    "contrast",
 84    "abbreviate_state",
 85    "state_names",
 86    "state_abbrs",
 87    # --- finalise_plot
 88    "finalise_plot",
 89    # --- line_plot
 90    "line_plot",
 91    # --- bar plot
 92    "bar_plot",
 93    # --- seastrend_plot
 94    "seastrend_plot",
 95    # --- postcovid_plot
 96    "postcovid_plot",
 97    # --- revision_plot
 98    "revision_plot",
 99    # --- run_plot
100    "run_plot",
101    # --- summary_plot
102    "summary_plot",
103    # --- growth_plot
104    "calc_growth",
105    "growth_plot",
106    "series_growth_plot",
107    # --- multi_plot
108    "multi_start",
109    "multi_column",
110    "plot_then_finalise",
111    # --- finaliser functions
112    "line_plot_finalise",
113    "bar_plot_finalise",
114    "seastrend_plot_finalise",
115    "postcovid_plot_finalise",
116    "revision_plot_finalise",
117    "summary_plot_finalise",
118    "growth_plot_finalise",
119    "series_growth_plot_finalise",
120    "run_plot_finalise",
121    # --- typing information
122    "FINALISE_KW_TYPES",
123    "BAR_KW_TYPES",
124    "LINE_KW_TYPES",
125    "SEASTREND_KW_TYPES",
126    "POSTCOVID_KW_TYPES",
127    "REVISION_KW_TYPES",
128    "RUN_KW_TYPES",
129    "SUMMARY_KW_TYPES",
130    "SERIES_GROWTH_KW_TYPES",
131    "GROWTH_KW_TYPES",
132    # --- The rest are internal use only
133)
134# __pdoc__: dict[str, Any] = {"test": False}  # hide submodules from documentation
__version__ = '0.1.3'
__author__ = 'Bryan Palmer'
def get_setting(setting: str) -> Any:
 86def get_setting(setting: str) -> Any:
 87    """
 88    Get a setting from the global settings.
 89
 90    Arguments:
 91    - setting: str - name of the setting to get. The possible settings are:
 92        - file_type: str - the file type to use for saving plots
 93        - figsize: tuple[float, float] - the figure size to use for plots
 94        - file_dpi: int - the DPI to use for saving plots
 95        - line_narrow: float - the line width for narrow lines
 96        - line_normal: float - the line width for normal lines
 97        - line_wide: float - the line width for wide lines
 98        - bar_width: float - the width of bars in bar plots
 99        - legend_font_size: float | str - the font size for legends
100        - legend: dict[str, Any] - the legend settings
101        - colors: dict[int, list[str]] - a dictionary of colors for
102          different numbers of lines
103        - chart_dir: str - the directory to save charts in
104
105    Raises:
106        - KeyError: if the setting is not found
107
108    Returns:
109        - value: Any - the value of the setting
110    """
111    if setting not in _mgplot_defaults:
112        raise KeyError(f"Setting '{setting}' not found in _mgplot_defaults.")
113    return _mgplot_defaults[setting]  # type: ignore[literal-required]

Get a setting from the global settings.

Arguments:

  • setting: str - name of the setting to get. The possible settings are:
    • file_type: str - the file type to use for saving plots
    • figsize: tuple[float, float] - the figure size to use for plots
    • file_dpi: int - the DPI to use for saving plots
    • line_narrow: float - the line width for narrow lines
    • line_normal: float - the line width for normal lines
    • line_wide: float - the line width for wide lines
    • bar_width: float - the width of bars in bar plots
    • legend_font_size: float | str - the font size for legends
    • legend: dict[str, Any] - the legend settings
    • colors: dict[int, list[str]] - a dictionary of colors for different numbers of lines
    • chart_dir: str - the directory to save charts in

Raises: - KeyError: if the setting is not found

Returns: - value: Any - the value of the setting

def set_setting(setting: str, value: Any) -> None:
116def set_setting(setting: str, value: Any) -> None:
117    """
118    Set a setting in the global settings.
119    Raises KeyError if the setting is not found.
120
121    Arguments:
122        - setting: str - name of the setting to set (see get_setting())
123        - value: Any - the value to set the setting to
124    """
125
126    if setting not in _mgplot_defaults:
127        raise KeyError(f"Setting '{setting}' not found in _mgplot_defaults.")
128    _mgplot_defaults[setting] = value  # type: ignore[literal-required]

Set a setting in the global settings. Raises KeyError if the setting is not found.

Arguments: - setting: str - name of the setting to set (see get_setting()) - value: Any - the value to set the setting to

def set_chart_dir(chart_dir: str) -> None:
147def set_chart_dir(chart_dir: str) -> None:
148    """
149    A function to set a global chart directory for finalise_plot(),
150    so that it does not need to be included as an argument in each
151    call to finalise_plot(). Create the directory if it does not exist.
152
153    Note: Path.mkdir() may raise an exception if a directory cannot be created.
154
155    Note: This is a wrapper for set_setting() to set the chart_dir setting, and
156    create the directory if it does not exist.
157
158    Arguments:
159        - chart_dir: str - the directory to set as the chart directory
160    """
161
162    if not chart_dir:
163        chart_dir = "."  # avoid the empty string
164    Path(chart_dir).mkdir(parents=True, exist_ok=True)
165    set_setting("chart_dir", chart_dir)

A function to set a global chart directory for finalise_plot(), so that it does not need to be included as an argument in each call to finalise_plot(). Create the directory if it does not exist.

Note: Path.mkdir() may raise an exception if a directory cannot be created.

Note: This is a wrapper for set_setting() to set the chart_dir setting, and create the directory if it does not exist.

Arguments: - chart_dir: str - the directory to set as the chart directory

def clear_chart_dir() -> None:
131def clear_chart_dir() -> None:
132    """
133    Remove all graph-image files from the global chart_dir.
134    This is a convenience function to remove all files from the
135    chart_dir directory. It does not remove the directory itself.
136    Note: the function creates the directory if it does not exist.
137    """
138
139    chart_dir = get_setting("chart_dir")
140    Path(chart_dir).mkdir(parents=True, exist_ok=True)
141    for ext in ("png", "svg", "jpg", "jpeg"):
142        for fs_object in Path(chart_dir).glob(f"*.{ext}"):
143            if fs_object.is_file():
144                fs_object.unlink()

Remove all graph-image files from the global chart_dir. This is a convenience function to remove all files from the chart_dir directory. It does not remove the directory itself. Note: the function creates the directory if it does not exist.

def get_color(s: str) -> str:
35def get_color(s: str) -> str:
36    """
37    Return a matplotlib color for a party label
38    or an Australian state/territory.
39    """
40
41    color_map = {
42        # --- Australian states and territories
43        ("wa", "western australia"): "gold",
44        ("sa", "south australia"): "red",
45        ("nt", "northern territory"): "#CC7722",  # ochre
46        ("nsw", "new south wales"): "deepskyblue",
47        ("act", "australian capital territory"): "blue",
48        ("vic", "victoria"): "navy",
49        ("tas", "tasmania"): "seagreen",  # bottle green #006A4E?
50        ("qld", "queensland"): "#c32148",  # a lighter maroon
51        ("australia", "aus"): "grey",
52        # --- political parties
53        ("dissatisfied",): "darkorange",  # must be before satisfied
54        ("satisfied",): "mediumblue",
55        (
56            "lnp",
57            "l/np",
58            "liberal",
59            "liberals",
60            "coalition",
61            "dutton",
62            "ley",
63            "liberal and/or nationals",
64        ): "royalblue",
65        (
66            "nat",
67            "nats",
68            "national",
69            "nationals",
70        ): "forestgreen",
71        (
72            "alp",
73            "labor",
74            "albanese",
75        ): "#dd0000",
76        (
77            "grn",
78            "green",
79            "greens",
80        ): "limegreen",
81        (
82            "other",
83            "oth",
84        ): "darkorange",
85    }
86
87    for find_me, return_me in color_map.items():
88        if any(x == s.lower() for x in find_me):
89            return return_me
90
91    return "darkgrey"

Return a matplotlib color for a party label or an Australian state/territory.

def get_party_palette(party_text: str) -> str:
14def get_party_palette(party_text: str) -> str:
15    """
16    Return a matplotlib color-map name based on party_text.
17    Works for Australian major political parties.
18    """
19
20    # Note: light to dark maps work best
21    match party_text.lower():
22        case "alp" | "labor":
23            return "Reds"
24        case "l/np" | "coalition":
25            return "Blues"
26        case "grn" | "green" | "greens":
27            return "Greens"
28        case "oth" | "other":
29            return "YlOrBr"
30        case "onp" | "one nation":
31            return "YlGnBu"
32    return "Purples"

Return a matplotlib color-map name based on party_text. Works for Australian major political parties.

def colorise_list(party_list: Iterable) -> list[str]:
94def colorise_list(party_list: Iterable) -> list[str]:
95    """
96    Return a list of party/state colors for a party_list.
97    """
98
99    return [get_color(x) for x in party_list]

Return a list of party/state colors for a party_list.

def contrast(orig_color: str) -> str:
102def contrast(orig_color: str) -> str:
103    """
104    Provide a constrasting color to any party color
105    generated by get_color() above.
106    """
107
108    new_color = "black"
109    match orig_color:
110        case "royalblue":
111            new_color = "indianred"
112        case "indianred":
113            new_color = "mediumblue"
114
115        case "darkorange":
116            new_color = "mediumblue"
117        case "mediumblue":
118            new_color = "darkorange"
119
120        case "mediumseagreen":
121            new_color = "darkblue"
122
123        case "darkgrey":
124            new_color = "hotpink"
125
126    return new_color

Provide a constrasting color to any party color generated by get_color() above.

def abbreviate_state(state: str) -> str:
157def abbreviate_state(state: str) -> str:
158    """
159    A function to abbreviate long-form state
160    names.
161
162    Arguments
163    -   state: the long-form state name.
164
165    Return the abbreviation for a state name.
166    """
167
168    return _state_names_multi.get(state.lower(), state)

A function to abbreviate long-form state names.

Arguments

  • state: the long-form state name.

Return the abbreviation for a state name.

state_names = ('New South Wales', 'Victoria', 'Queensland', 'South Australia', 'Western Australia', 'Tasmania', 'Northern Territory', 'Australian Capital Territory')
state_abbrs = ('NSW', 'Vic', 'Qld', 'SA', 'WA', 'Tas', 'NT', 'ACT')
def finalise_plot(axes: matplotlib.axes._axes.Axes, **kwargs) -> None:
314def finalise_plot(axes: Axes, **kwargs) -> None:
315    """
316    A function to finalise and save plots to the file system. The filename
317    for the saved plot is constructed from the global chart_dir, the plot's title,
318    any specified tag text, and the file_type for the plot.
319
320    Arguments:
321    - axes - matplotlib axes object - required
322    - kwargs
323        - title: str - plot title, also used to create the save file name
324        - xlabel: str | None - text label for the x-axis
325        - ylabel: str | None - label for the y-axis
326        - pre_tag: str - text before the title in file name
327        - tag: str - text after the title in the file name
328          (useful for ensuring that same titled charts do not over-write)
329        - chart_dir: str - location of the chart directory
330        - file_type: str - specify a file type - eg. 'png' or 'svg'
331        - lfooter: str - text to display on bottom left of plot
332        - rfooter: str - text to display of bottom right of plot
333        - lheader: str - text to display on top left of plot
334        - rheader: str - text to display of top right of plot
335        - figsize: tuple[float, float] - figure size in inches - eg. (8, 4)
336        - show: bool - whether to show the plot or not
337        - zero_y: bool - ensure y=0 is included in the plot.
338        - y0: bool - highlight the y=0 line on the plot (if in scope)
339        - x0: bool - highlights the x=0 line on the plot
340        - dont_save: bool - dont save the plot to the file system
341        - dont_close: bool - dont close the plot
342        - dpi: int - dots per inch for the saved chart
343        - legend: bool | dict - if dict, use as the arguments to pass to axes.legend(),
344          if True pass the global default arguments to axes.legend()
345        - axhspan: dict - arguments to pass to axes.axhspan()
346        - axvspan: dict - arguments to pass to axes.axvspan()
347        - axhline: dict - arguments to pass to axes.axhline()
348        - axvline: dict - arguments to pass to axes.axvline()
349        - ylim: tuple[float, float] - set lower and upper y-axis limits
350        - xlim: tuple[float, float] - set lower and upper x-axis limits
351        - preserve_lims: bool - if True, preserve the original axes limits,
352          lims saved at the start, and restored after the tight layout
353        - remove_legend: bool | None - if True, remove the legend from the plot
354        - report_kwargs: bool - if True, report the kwargs used in this function
355
356     Returns:
357        - None
358    """
359
360    # --- check the kwargs
361    me = "finalise_plot"
362    report_kwargs(called_from=me, **kwargs)
363    kwargs = validate_kwargs(FINALISE_KW_TYPES, me, **kwargs)
364
365    # --- sanity checks
366    if len(axes.get_children()) < 1:
367        print("Warning: finalise_plot() called with empty axes, which was ignored.")
368        return
369
370    # --- remember axis-limits should we need to restore thems
371    xlim, ylim = axes.get_xlim(), axes.get_ylim()
372
373    # margins
374    axes.margins(0.02)
375    axes.autoscale(tight=False)  # This is problematic ...
376
377    _apply_kwargs(axes, **kwargs)
378
379    # tight layout and save the figure
380    fig = axes.figure
381    if not isinstance(fig, mpl.figure.SubFigure):  # should never be a SubFigure
382        fig.tight_layout(pad=1.1)
383        if PRESERVE_LIMS in kwargs and kwargs[PRESERVE_LIMS]:
384            # restore the original limits of the axes
385            axes.set_xlim(xlim)
386            axes.set_ylim(ylim)
387        _apply_late_kwargs(axes, **kwargs)
388        legend = axes.get_legend()
389        if legend and kwargs.get(REMOVE_LEGEND, False):
390            legend.remove()
391        _save_to_file(fig, **kwargs)
392
393    # show the plot in Jupyter Lab
394    if SHOW in kwargs and kwargs[SHOW]:
395        plt.show()
396
397    # And close
398    closing = True if DONT_CLOSE not in kwargs else not kwargs[DONT_CLOSE]
399    if closing:
400        plt.close()

A function to finalise and save plots to the file system. The filename for the saved plot is constructed from the global chart_dir, the plot's title, any specified tag text, and the file_type for the plot.

Arguments:

  • axes - matplotlib axes object - required
  • kwargs
    • title: str - plot title, also used to create the save file name
    • xlabel: str | None - text label for the x-axis
    • ylabel: str | None - label for the y-axis
    • pre_tag: str - text before the title in file name
    • tag: str - text after the title in the file name (useful for ensuring that same titled charts do not over-write)
    • chart_dir: str - location of the chart directory
    • file_type: str - specify a file type - eg. 'png' or 'svg'
    • lfooter: str - text to display on bottom left of plot
    • rfooter: str - text to display of bottom right of plot
    • lheader: str - text to display on top left of plot
    • rheader: str - text to display of top right of plot
    • figsize: tuple[float, float] - figure size in inches - eg. (8, 4)
    • show: bool - whether to show the plot or not
    • zero_y: bool - ensure y=0 is included in the plot.
    • y0: bool - highlight the y=0 line on the plot (if in scope)
    • x0: bool - highlights the x=0 line on the plot
    • dont_save: bool - dont save the plot to the file system
    • dont_close: bool - dont close the plot
    • dpi: int - dots per inch for the saved chart
    • legend: bool | dict - if dict, use as the arguments to pass to axes.legend(), if True pass the global default arguments to axes.legend()
    • axhspan: dict - arguments to pass to axes.axhspan()
    • axvspan: dict - arguments to pass to axes.axvspan()
    • axhline: dict - arguments to pass to axes.axhline()
    • axvline: dict - arguments to pass to axes.axvline()
    • ylim: tuple[float, float] - set lower and upper y-axis limits
    • xlim: tuple[float, float] - set lower and upper x-axis limits
    • preserve_lims: bool - if True, preserve the original axes limits, lims saved at the start, and restored after the tight layout
    • remove_legend: bool | None - if True, remove the legend from the plot
    • report_kwargs: bool - if True, report the kwargs used in this function

Returns: - None

def line_plot(data: ~DataT, **kwargs) -> matplotlib.axes._axes.Axes:
147def line_plot(data: DataT, **kwargs) -> Axes:
148    """
149    Build a single plot from the data passed in.
150    This can be a single- or multiple-line plot.
151    Return the axes object for the build.
152
153    Agruments:
154    - data: DataFrame | Series - data to plot
155    - kwargs:
156        /* chart wide arguments */
157        - ax: Axes | None - axes to plot on (optional)
158        /* individual line arguments */
159        - dropna: bool | list[bool] - whether to delete NAs frm the
160          data before plotting [optional]
161        - color: str | list[str] - line colors.
162        - width: float | list[float] - line widths [optional].
163        - style: str | list[str] - line styles [optional].
164        - alpha: float | list[float] - line transparencies [optional].
165        - marker: str | list[str] - line markers [optional].
166        - marker_size: float | list[float] - line marker sizes [optional].
167        /* end of line annotation arguments */
168        - annotate: bool | list[bool] - whether to annotate a series.
169        - rounding: int | bool | list[int | bool] - number of decimal places
170          to round an annotation. If True, a default between 0 and 2 is
171          used.
172        - fontsize: int | str | list[int | str] - font size for the
173          annotation.
174        - fontname: str - font name for the annotation.
175        - rotation: int | float | list[int | float] - rotation of the
176          annotation text.
177        - drawstyle: str | list[str] - matplotlib line draw styles.
178        - annotate_color: str | list[str] | bool | list[bool] - color
179          for the annotation text.  If True, the same color as the line.
180
181    Returns:
182    - axes: Axes - the axes object for the plot
183    """
184
185    # --- check the kwargs
186    me = "line_plot"
187    report_kwargs(called_from=me, **kwargs)
188    kwargs = validate_kwargs(LINE_KW_TYPES, me, **kwargs)
189
190    # --- check the data
191    data = check_clean_timeseries(data, me)
192    df = DataFrame(data)  # we are only plotting DataFrames
193    df, kwargs = constrain_data(df, **kwargs)
194
195    # --- some special defaults
196    kwargs[LABEL_SERIES] = (
197        kwargs.get(LABEL_SERIES, True)
198        if len(df.columns) > 1
199        else kwargs.get(LABEL_SERIES, False)
200    )
201
202    # --- Let's plot
203    axes, kwargs = get_axes(**kwargs)  # get the axes to plot on
204    if df.empty or df.isna().all().all():
205        # Note: finalise plot should ignore an empty axes object
206        print(f"Warning: No data to plot in {me}().")
207        return axes
208
209    # --- get the arguments for each line we will plot ...
210    item_count = len(df.columns)
211    num_data_points = len(df)
212    swce, kwargs = _get_style_width_color_etc(item_count, num_data_points, **kwargs)
213
214    for i, column in enumerate(df.columns):
215        series = df[column]
216        series = series.dropna() if DROPNA in swce and swce[DROPNA][i] else series
217        if series.empty or series.isna().all():
218            print(f"Warning: No data to plot for {column} in line_plot().")
219            continue
220
221        series.plot(
222            # Note: pandas will plot PeriodIndex against their ordinal values
223            ls=swce[STYLE][i],
224            lw=swce[WIDTH][i],
225            color=swce[COLOR][i],
226            alpha=swce[ALPHA][i],
227            marker=swce[MARKER][i],
228            ms=swce[MARKERSIZE][i],
229            drawstyle=swce[DRAWSTYLE][i],
230            label=(
231                column
232                if LABEL_SERIES in swce and swce[LABEL_SERIES][i]
233                else f"_{column}_"
234            ),
235            ax=axes,
236        )
237
238        if swce[ANNOTATE][i] is None or not swce[ANNOTATE][i]:
239            continue
240
241        color = (
242            swce[COLOR][i]
243            if swce[ANNOTATE_COLOR][i] is True
244            else swce[ANNOTATE_COLOR][i]
245        )
246        annotate_series(
247            series,
248            axes,
249            color=color,
250            rounding=swce[ROUNDING][i],
251            fontsize=swce[FONTSIZE][i],
252            fontname=swce[FONTNAME][i],
253            rotation=swce[ROTATION][i],
254        )
255
256    return axes

Build a single plot from the data passed in. This can be a single- or multiple-line plot. Return the axes object for the build.

Agruments:

  • data: DataFrame | Series - data to plot
  • kwargs: /* chart wide arguments /
    • ax: Axes | None - axes to plot on (optional) / individual line arguments /
    • dropna: bool | list[bool] - whether to delete NAs frm the data before plotting [optional]
    • color: str | list[str] - line colors.
    • width: float | list[float] - line widths [optional].
    • style: str | list[str] - line styles [optional].
    • alpha: float | list[float] - line transparencies [optional].
    • marker: str | list[str] - line markers [optional].
    • marker_size: float | list[float] - line marker sizes [optional]. / end of line annotation arguments */
    • annotate: bool | list[bool] - whether to annotate a series.
    • rounding: int | bool | list[int | bool] - number of decimal places to round an annotation. If True, a default between 0 and 2 is used.
    • fontsize: int | str | list[int | str] - font size for the annotation.
    • fontname: str - font name for the annotation.
    • rotation: int | float | list[int | float] - rotation of the annotation text.
    • drawstyle: str | list[str] - matplotlib line draw styles.
    • annotate_color: str | list[str] | bool | list[bool] - color for the annotation text. If True, the same color as the line.

Returns:

  • axes: Axes - the axes object for the plot
def bar_plot(data: ~DataT, **kwargs) -> matplotlib.axes._axes.Axes:
213def bar_plot(
214    data: DataT,
215    **kwargs,
216) -> Axes:
217    """
218    Create a bar plot from the given data. Each column in the DataFrame
219    will be stacked on top of each other, with positive values above
220    zero and negative values below zero.
221
222    Parameters
223    - data: Series - The data to plot. Can be a DataFrame or a Series.
224    - **kwargs: dict Additional keyword arguments for customization.
225    # --- options for the entire bar plot
226    ax: Axes - axes to plot on, or None for new axes
227    stacked: bool - if True, the bars will be stacked. If False, they will be grouped.
228    max_ticks: int - maximum number of ticks on the x-axis (for PeriodIndex only)
229    plot_from: int | PeriodIndex - if provided, the plot will start from this index.
230    # --- options for each bar ...
231    color: str | list[str] - the color of the bars (or separate colors for each series
232    label_series: bool | list[bool] - if True, the series will be labeled in the legend
233    width: float | list[float] - the width of the bars
234    # - options for bar annotations
235    annotate: bool - If True them annotate the bars with their values.
236    fontsize: int | float | str - font size of the annotations
237    fontname: str - font name of the annotations
238    rounding: int - number of decimal places to round to
239    annotate_color: str  - color of annotations
240    rotation: int | float - rotation of annotations in degrees
241    above: bool - if True, annotations are above the bar, else within the bar
242
243    Note: This function does not assume all data is timeseries with a PeriodIndex,
244
245    Returns
246    - axes: Axes - The axes for the plot.
247    """
248
249    # --- check the kwargs
250    me = "bar_plot"
251    report_kwargs(called_from=me, **kwargs)
252    kwargs = validate_kwargs(BAR_KW_TYPES, me, **kwargs)
253
254    # --- get the data
255    # no call to check_clean_timeseries here, as bar plots are not
256    # necessarily timeseries data. If the data is a Series, it will be
257    # converted to a DataFrame with a single column.
258    df = DataFrame(data)  # really we are only plotting DataFrames
259    df, kwargs = constrain_data(df, **kwargs)
260    item_count = len(df.columns)
261
262    # --- deal with complete PeriodIdex indicies
263    if not is_categorical(df):
264        print(
265            "Warning: bar_plot is not designed for incomplete or non-categorical data indexes."
266        )
267    saved_pi = map_periodindex(df)
268    if saved_pi is not None:
269        df = saved_pi[0]  # extract the reindexed DataFrame from the PeriodIndex
270
271    # --- set up the default arguments
272    chart_defaults: dict[str, Any] = {
273        STACKED: False,
274        MAX_TICKS: 10,
275        LABEL_SERIES: item_count > 1,
276    }
277    chart_args = {k: kwargs.get(k, v) for k, v in chart_defaults.items()}
278
279    bar_defaults: dict[str, Any] = {
280        COLOR: get_color_list(item_count),
281        WIDTH: get_setting("bar_width"),
282        LABEL_SERIES: (item_count > 1),
283    }
284    above = kwargs.get(ABOVE, False)
285    anno_args = {
286        ANNOTATE: kwargs.get(ANNOTATE, False),
287        FONTSIZE: kwargs.get(FONTSIZE, "small"),
288        FONTNAME: kwargs.get(FONTNAME, "Helvetica"),
289        ROTATION: kwargs.get(ROTATION, 0),
290        ROUNDING: kwargs.get(ROUNDING, True),
291        COLOR: kwargs.get(ANNOTATE_COLOR, "black" if above else "white"),
292        ABOVE: above,
293    }
294    bar_args, remaining_kwargs = apply_defaults(item_count, bar_defaults, kwargs)
295
296    # --- plot the data
297    axes, _rkwargs = get_axes(**remaining_kwargs)
298    if chart_args[STACKED]:
299        stacked(axes, df, anno_args, **bar_args)
300    else:
301        grouped(axes, df, anno_args, **bar_args)
302
303    # --- handle complete periodIndex data and label rotation
304    if saved_pi is not None:
305        set_labels(axes, saved_pi[1], chart_args["max_ticks"])
306    else:
307        plt.xticks(rotation=90)
308
309    return axes

Create a bar plot from the given data. Each column in the DataFrame will be stacked on top of each other, with positive values above zero and negative values below zero.

Parameters

  • data: Series - The data to plot. Can be a DataFrame or a Series.
  • **kwargs: dict Additional keyword arguments for customization.

--- options for the entire bar plot

ax: Axes - axes to plot on, or None for new axes stacked: bool - if True, the bars will be stacked. If False, they will be grouped. max_ticks: int - maximum number of ticks on the x-axis (for PeriodIndex only) plot_from: int | PeriodIndex - if provided, the plot will start from this index.

--- options for each bar ...

color: str | list[str] - the color of the bars (or separate colors for each series label_series: bool | list[bool] - if True, the series will be labeled in the legend width: float | list[float] - the width of the bars

- options for bar annotations

annotate: bool - If True them annotate the bars with their values. fontsize: int | float | str - font size of the annotations fontname: str - font name of the annotations rounding: int - number of decimal places to round to annotate_color: str - color of annotations rotation: int | float - rotation of annotations in degrees above: bool - if True, annotations are above the bar, else within the bar

Note: This function does not assume all data is timeseries with a PeriodIndex,

Returns

  • axes: Axes - The axes for the plot.
def seastrend_plot(data: ~DataT, **kwargs) -> matplotlib.axes._axes.Axes:
28def seastrend_plot(data: DataT, **kwargs) -> Axes:
29    """
30    Publish a DataFrame, where the first column is seasonally
31    adjusted data, and the second column is trend data.
32
33    Aguments:
34    - data: DataFrame - the data to plot with the first column
35      being the seasonally adjusted data, and the second column
36      being the trend data.
37    The remaining arguments are the same as those passed to
38    line_plot().
39
40    Returns:
41    - a matplotlib Axes object
42    """
43
44    # Note: we will rely on the line_plot() function to do most of the work.
45    # including constraining the data to the plot_from keyword argument.
46
47    # --- check the kwargs
48    me = "seastrend_plot"
49    report_kwargs(called_from=me, **kwargs)
50    kwargs = validate_kwargs(SEASTREND_KW_TYPES, me, **kwargs)
51
52    # --- check the data
53    data = check_clean_timeseries(data, me)
54    if len(data.columns) < 2:
55        raise ValueError(
56            "seas_trend_plot() expects a DataFrame data item with at least 2 columns."
57        )
58
59    # --- defaults if not in kwargs
60    colors = kwargs.pop(COLOR, get_color_list(2))
61    widths = kwargs.pop(WIDTH, [get_setting("line_normal"), get_setting("line_wide")])
62    styles = kwargs.pop(STYLE, ["-", "-"])
63    annotations = kwargs.pop(ANNOTATE, [True, False])
64    rounding = kwargs.pop(ROUNDING, True)
65
66    # series breaks are common in seas-trend data
67    kwargs[DROPNA] = kwargs.pop(DROPNA, False)
68
69    axes = line_plot(
70        data,
71        color=colors,
72        width=widths,
73        style=styles,
74        annotate=annotations,
75        rounding=rounding,
76        **kwargs,
77    )
78
79    return axes

Publish a DataFrame, where the first column is seasonally adjusted data, and the second column is trend data.

Aguments:

  • data: DataFrame - the data to plot with the first column being the seasonally adjusted data, and the second column being the trend data. The remaining arguments are the same as those passed to line_plot().

Returns:

  • a matplotlib Axes object
def postcovid_plot(data: ~DataT, **kwargs) -> matplotlib.axes._axes.Axes:
 65def postcovid_plot(data: DataT, **kwargs) -> Axes:
 66    """
 67    Plots a series with a PeriodIndex.
 68
 69    Arguments
 70    - data - the series to be plotted (note that this function
 71      is designed to work with a single series, not a DataFrame).
 72    - **kwargs - same as for line_plot() and finalise_plot().
 73
 74    Raises:
 75    - TypeError if series is not a pandas Series
 76    - TypeError if series does not have a PeriodIndex
 77    - ValueError if series does not have a D, M or Q frequency
 78    - ValueError if regression start is after regression end
 79    """
 80
 81    # --- check the kwargs
 82    me = "postcovid_plot"
 83    report_kwargs(called_from=me, **kwargs)
 84    kwargs = validate_kwargs(POSTCOVID_KW_TYPES, me, **kwargs)
 85
 86    # --- check the data
 87    data = check_clean_timeseries(data, me)
 88    if not isinstance(data, Series):
 89        raise TypeError("The series argument must be a pandas Series")
 90    series: Series = data
 91    series_index = PeriodIndex(series.index)  # syntactic sugar for type hinting
 92    if series_index.freqstr[:1] not in ("Q", "M", "D"):
 93        raise ValueError("The series index must have a D, M or Q freq")
 94    # rely on line_plot() to validate kwargs
 95    if PLOT_FROM in kwargs:
 96        print("Warning: the 'plot_from' argument is ignored in postcovid_plot().")
 97        del kwargs[PLOT_FROM]
 98
 99    # --- plot COVID counterfactural
100    freq = PeriodIndex(series.index).freqstr  # syntactic sugar for type hinting
101    match freq[0]:
102        case "Q":
103            start_regression = Period("2014Q4", freq=freq)
104            end_regression = Period("2019Q4", freq=freq)
105        case "M":
106            start_regression = Period("2015-01", freq=freq)
107            end_regression = Period("2020-01", freq=freq)
108        case "D":
109            start_regression = Period("2015-01-01", freq=freq)
110            end_regression = Period("2020-01-01", freq=freq)
111
112    start_regression = Period(kwargs.pop(START_R, start_regression), freq=freq)
113    end_regression = Period(kwargs.pop(END_R, end_regression), freq=freq)
114    if start_regression >= end_regression:
115        raise ValueError("Start period must be before end period")
116
117    # --- combine data and projection
118    recent = series[series.index >= start_regression].copy()
119    recent.name = "Series"
120    projection = get_projection(recent, end_regression)
121    projection.name = "Pre-COVID projection"
122    data_set = DataFrame([projection, recent]).T
123
124    # --- activate plot settings
125    kwargs[WIDTH] = kwargs.pop(
126        WIDTH, (get_setting("line_normal"), get_setting("line_wide"))
127    )  # series line is thicker than projection
128    kwargs[STYLE] = kwargs.pop(STYLE, ("--", "-"))  # dashed regression line
129    kwargs[LABEL_SERIES] = kwargs.pop(LABEL_SERIES, True)
130    kwargs[ANNOTATE] = kwargs.pop(ANNOTATE, (False, True))  # annotate series only
131    kwargs[COLOR] = kwargs.pop(COLOR, ("darkblue", "#dd0000"))
132
133    return line_plot(
134        data_set,
135        **kwargs,
136    )

Plots a series with a PeriodIndex.

Arguments

  • data - the series to be plotted (note that this function is designed to work with a single series, not a DataFrame).
  • **kwargs - same as for line_plot() and finalise_plot().

Raises:

  • TypeError if series is not a pandas Series
  • TypeError if series does not have a PeriodIndex
  • ValueError if series does not have a D, M or Q frequency
  • ValueError if regression start is after regression end
def revision_plot(data: ~DataT, **kwargs) -> matplotlib.axes._axes.Axes:
34def revision_plot(data: DataT, **kwargs) -> Axes:
35    """
36    Plot the revisions to ABS data.
37
38    Arguments
39    data: pd.DataFrame - the data to plot, the DataFrame has a
40        column for each data revision
41    kwargs - additional keyword arguments for the line_plot function.
42    """
43
44    # --- check the kwargs and data
45    me = "revision_plot"
46    report_kwargs(called_from=me, **kwargs)
47    kwargs = validate_kwargs(REVISION_KW_TYPES, me, **kwargs)
48
49    data = check_clean_timeseries(data, me)
50
51    # --- additional checks
52    if not isinstance(data, DataFrame):
53        print(
54            f"{me} requires a DataFrame with columns for each revision, "
55            "not a Series or other type."
56        )
57
58    # --- critical defaults
59    kwargs[PLOT_FROM] = kwargs.get(PLOT_FROM, -15)
60    kwargs[ANNOTATE] = kwargs.get(ANNOTATE, True)
61    kwargs[ANNOTATE_COLOR] = kwargs.get(ANNOTATE_COLOR, "black")
62    kwargs[ROUNDING] = kwargs.get(ROUNDING, 3)
63
64    # --- plot
65    axes = line_plot(data, **kwargs)
66
67    return axes

Plot the revisions to ABS data.

Arguments data: pd.DataFrame - the data to plot, the DataFrame has a column for each data revision kwargs - additional keyword arguments for the line_plot function.

def run_plot(data: ~DataT, **kwargs) -> matplotlib.axes._axes.Axes:
117def run_plot(data: DataT, **kwargs) -> Axes:
118    """Plot a series of percentage rates, highlighting the increasing runs.
119
120    Arguments
121     - data - ordered pandas Series of percentages, with PeriodIndex
122     - **kwargs
123        - threshold - float - used to ignore micro noise near zero
124          (for example, threshhold=0.01)
125        - round - int - rounding for highlight text
126        - highlight - str or Sequence[str] - color(s) for highlighting the
127          runs, two colors can be specified in a list if direction is "both"
128        - direction - str - whether the highlight is for an upward
129          or downward or both runs. Options are "up", "down" or "both".
130        - in addition the **kwargs for line_plot are accepted.
131
132    Return
133     - matplotlib Axes object"""
134
135    # --- check the kwargs
136    me = "run_plot"
137    report_kwargs(called_from=me, **kwargs)
138    kwargs = validate_kwargs(RUN_KW_TYPES, me, **kwargs)
139
140    # --- check the data
141    series = check_clean_timeseries(data, me)
142    if not isinstance(series, Series):
143        raise TypeError("series must be a pandas Series for run_plot()")
144    series, kwargs = constrain_data(series, **kwargs)
145
146    # --- default arguments - in **kwargs
147    kwargs[THRESHOLD] = kwargs.get(THRESHOLD, 0.1)
148    kwargs[DIRECTION] = kwargs.get(DIRECTION, "both")
149    kwargs[ROUNDING] = kwargs.get(ROUNDING, 2)
150    kwargs[HIGHLIGHT] = kwargs.get(
151        HIGHLIGHT, ("gold", "skyblue") if kwargs[DIRECTION] == "both" else "gold"
152    )
153    kwargs[COLOR] = kwargs.get(COLOR, "darkblue")
154
155    # --- plot the line
156    kwargs[DRAWSTYLE] = kwargs.get(DRAWSTYLE, "steps-post")
157    lp_kwargs = limit_kwargs(LINE_KW_TYPES, **kwargs)
158    axes = line_plot(series, **lp_kwargs)
159
160    # plot the runs
161    match kwargs[DIRECTION]:
162        case "up":
163            _plot_runs(axes, series, up=True, **kwargs)
164        case "down":
165            _plot_runs(axes, series, up=False, **kwargs)
166        case "both":
167            _plot_runs(axes, series, up=True, **kwargs)
168            _plot_runs(axes, series, up=False, **kwargs)
169        case _:
170            raise ValueError(
171                f"Invalid value for direction: {kwargs[DIRECTION]}. "
172                "Expected 'up', 'down', or 'both'."
173            )
174    return axes

Plot a series of percentage rates, highlighting the increasing runs.

Arguments

  • data - ordered pandas Series of percentages, with PeriodIndex
  • *kwargs
    • threshold - float - used to ignore micro noise near zero (for example, threshhold=0.01)
    • round - int - rounding for highlight text
    • highlight - str or Sequence[str] - color(s) for highlighting the runs, two colors can be specified in a list if direction is "both"
    • direction - str - whether the highlight is for an upward or downward or both runs. Options are "up", "down" or "both".
    • in addition the *kwargs for line_plot are accepted.

Return

  • matplotlib Axes object
def summary_plot(data: ~DataT, **kwargs) -> matplotlib.axes._axes.Axes:
201def summary_plot(
202    data: DataT,  # summary data
203    **kwargs,
204) -> Axes:
205    """Plot a summary of historical data for a given DataFrame.
206
207    Args:
208    - summary: DataFrame containing the summary data. The column names are
209      used as labels for the plot.
210    - kwargs: additional arguments for the plot, including:
211        - plot_from: int | Period | None
212        - verbose: if True, print the summary data.
213        - middle: proportion of data to highlight (default is 0.8).
214        - plot_types: list of plot types to generate.
215
216
217    Returns Axes.
218    """
219
220    # --- check the kwargs
221    me = "summary_plot"
222    report_kwargs(called_from=me, **kwargs)
223    kwargs = validate_kwargs(SUMMARY_KW_TYPES, me, **kwargs)
224
225    # --- check the data
226    data = check_clean_timeseries(data, me)
227    if not isinstance(data, DataFrame):
228        raise TypeError("data must be a pandas DataFrame for summary_plot()")
229    df = DataFrame(data)  # syntactic sugar for type hinting
230
231    # --- optional arguments
232    verbose = kwargs.pop("verbose", False)
233    middle = float(kwargs.pop("middle", 0.8))
234    plot_type = kwargs.pop("plot_type", ZSCORES)
235    kwargs["legend"] = kwargs.get(
236        "legend",
237        {
238            # put the legend below the x-axis label
239            "loc": "upper center",
240            "fontsize": "xx-small",
241            "bbox_to_anchor": (0.5, -0.125),
242            "ncol": 4,
243        },
244    )
245
246    # get the data, calculate z-scores and scaled scores based on the start period
247    subset, kwargs = constrain_data(df, **kwargs)
248    z_scores, z_scaled = _calculate_z(subset, middle, verbose=verbose)
249
250    # plot as required by the plot_types argument
251    adjusted = z_scores if plot_type == ZSCORES else z_scaled
252    ax = _horizontal_bar_plot(subset, adjusted, middle, plot_type, kwargs)
253    ax.tick_params(axis="y", labelsize="small")
254    make_legend(ax, kwargs["legend"])
255    ax.set_xlim(kwargs.get("xlim", None))  # provide space for the labels
256
257    return ax

Plot a summary of historical data for a given DataFrame.

Args:

  • summary: DataFrame containing the summary data. The column names are used as labels for the plot.
  • kwargs: additional arguments for the plot, including:
    • plot_from: int | Period | None
    • verbose: if True, print the summary data.
    • middle: proportion of data to highlight (default is 0.8).
    • plot_types: list of plot types to generate.

Returns Axes.

def calc_growth(series: pandas.core.series.Series) -> pandas.core.frame.DataFrame:
155def calc_growth(series: Series) -> DataFrame:
156    """
157    Calculate annual and periodic growth for a pandas Series,
158    where the index is a PeriodIndex.
159
160    Args:
161    -   series: A pandas Series with an appropriate PeriodIndex.
162
163    Returns a two column DataFrame:
164
165    Raises
166    -   TypeError if the series is not a pandas Series.
167    -   TypeError if the series index is not a PeriodIndex.
168    -   ValueError if the series is empty.
169    -   ValueError if the series index does not have a frequency of Q, M, or D.
170    -   ValueError if the series index has duplicates.
171    """
172
173    # --- sanity checks
174    if not isinstance(series, Series):
175        raise TypeError("The series argument must be a pandas Series")
176    if not isinstance(series.index, PeriodIndex):
177        raise TypeError("The series index must be a pandas PeriodIndex")
178    if series.empty:
179        raise ValueError("The series argument must not be empty")
180    if series.index.freqstr[0] not in ("Q", "M", "D"):
181        raise ValueError("The series index must have a frequency of Q, M, or D")
182    if series.index.has_duplicates:
183        raise ValueError("The series index must not have duplicate values")
184
185    # --- ensure the index is complete and the date is sorted
186    complete = period_range(start=series.index.min(), end=series.index.max())
187    series = series.reindex(complete, fill_value=nan)
188    series = series.sort_index(ascending=True)
189
190    # --- calculate annual and periodic growth
191    ppy = {"Q": 4, "M": 12, "D": 365}[PeriodIndex(series.index).freqstr[:1]]
192    annual = series.pct_change(periods=ppy) * 100
193    periodic = series.pct_change(periods=1) * 100
194    periodic_name = {4: "Quarterly", 12: "Monthly", 365: "Daily"}[ppy] + " Growth"
195    return DataFrame(
196        {
197            "Annual Growth": annual,
198            periodic_name: periodic,
199        }
200    )

Calculate annual and periodic growth for a pandas Series, where the index is a PeriodIndex.

Args:

  • series: A pandas Series with an appropriate PeriodIndex.

Returns a two column DataFrame:

Raises

  • TypeError if the series is not a pandas Series.
  • TypeError if the series index is not a PeriodIndex.
  • ValueError if the series is empty.
  • ValueError if the series index does not have a frequency of Q, M, or D.
  • ValueError if the series index has duplicates.
def growth_plot(data: ~DataT, **kwargs) -> matplotlib.axes._axes.Axes:
203def growth_plot(
204    data: DataT,
205    **kwargs,
206) -> Axes:
207    """
208        Plot annual growth (as a line) and periodic growth (as bars)
209        on the same axes.
210
211        Args:
212        -   data: A pandas DataFrame with two columns:
213        -   kwargs:
214            # --- common options
215            ax: Axes | None -- the matplotlib Axes to plot on, or None to create a new one.
216            plot_from: Period | int | None -- the period to start plotting from
217            label_series: bool -- whether to label the series in the legend.
218            max_ticks: int -- maximum number of ticks on the x-axis
219            # --- options passed to the line plot
220            line_width: float | int  -- the width of the line
221            line_color: str  -- the color of the line
222            line_style: str  -- the style of the line
223            annotate_line: None | bool -- whether to annotate the end of the line
224            line_rounding: bool | int  -- rounding for line annotation
225            line_fontsize: str | int | float  -- fontsize for the line annotation
226            line_fontname: str  -- font name for the line annotation
227            line_anno_color: str | bool | None  -- color for the line annotation
228            # --- options passed to the bar plot
229            bar_width: float,
230            bar_color: str,
231            annotate_bars: None | bool -- whether to annotate the bars
232            above: bool -- whether to place the bar annotations above the bars
233            bar_fontsize: str | int | float -- fontsize for the bar annotations
234            bar_fontname: str -- font name for the bar annotations
235            bar_rounding: bool | int -- rounding for bar annotation
236            bar_anno_color: str | None -- color for the bar annotation
237            bar_rotation: int | float -- rotation for the bar annotation
238    }
239        Returns:
240        -   axes: The matplotlib Axes object.
241
242        Raises:
243        -   TypeError if the annual and periodic arguments are not pandas Series.
244        -   TypeError if the annual index is not a PeriodIndex.
245        -   ValueError if the annual and periodic series do not have the same index.
246    """
247
248    # --- check the kwargs
249    me = "growth_plot"
250    report_kwargs(called_from=me, **kwargs)
251    kwargs = validate_kwargs(GROWTH_KW_TYPES, me, **kwargs)
252
253    # --- data checks
254    data = check_clean_timeseries(data, me)
255    if len(data.columns) != 2:
256        raise TypeError("The data argument must be a pandas DataFrame with two columns")
257    data, kwargs = constrain_data(data, **kwargs)
258
259    # --- get the series of interest ...
260    annual = data[data.columns[0]]
261    periodic = data[data.columns[1]]
262
263    # --- series names
264    annual.name = "Annual Growth"
265    periodic.name = {"M": "Monthly", "Q": "Quarterly", "D": "Daily"}[
266        PeriodIndex(periodic.index).freqstr[:1]
267    ] + " Growth"
268
269    # --- convert PeriodIndex periodic growth data to integer indexed data.
270    saved_pi = map_periodindex(periodic)
271    if saved_pi is not None:
272        periodic = saved_pi[0]  # extract the reindexed DataFrame
273
274    # --- simple bar chart for the periodic growth
275    if BAR_ANNO_COLOR not in kwargs or kwargs[BAR_ANNO_COLOR] is None:
276        kwargs[BAR_ANNO_COLOR] = "black" if kwargs.get(ABOVE, False) else "white"
277    selected = package_kwargs(to_bar_plot, **kwargs)
278    axes = bar_plot(periodic, **selected)
279
280    # --- and now the annual growth as a line
281    selected = package_kwargs(to_line_plot, **kwargs)
282    line_plot(annual, ax=axes, **selected)
283
284    # --- fix the x-axis labels
285    if saved_pi is not None:
286        set_labels(axes, saved_pi[1], kwargs.get("max_ticks", 10))
287
288    # --- and done ...
289    return axes

Plot annual growth (as a line) and periodic growth (as bars) on the same axes.

Args:
-   data: A pandas DataFrame with two columns:
-   kwargs:
    # --- common options
    ax: Axes | None -- the matplotlib Axes to plot on, or None to create a new one.
    plot_from: Period | int | None -- the period to start plotting from
    label_series: bool -- whether to label the series in the legend.
    max_ticks: int -- maximum number of ticks on the x-axis
    # --- options passed to the line plot
    line_width: float | int  -- the width of the line
    line_color: str  -- the color of the line
    line_style: str  -- the style of the line
    annotate_line: None | bool -- whether to annotate the end of the line
    line_rounding: bool | int  -- rounding for line annotation
    line_fontsize: str | int | float  -- fontsize for the line annotation
    line_fontname: str  -- font name for the line annotation
    line_anno_color: str | bool | None  -- color for the line annotation
    # --- options passed to the bar plot
    bar_width: float,
    bar_color: str,
    annotate_bars: None | bool -- whether to annotate the bars
    above: bool -- whether to place the bar annotations above the bars
    bar_fontsize: str | int | float -- fontsize for the bar annotations
    bar_fontname: str -- font name for the bar annotations
    bar_rounding: bool | int -- rounding for bar annotation
    bar_anno_color: str | None -- color for the bar annotation
    bar_rotation: int | float -- rotation for the bar annotation

} Returns: - axes: The matplotlib Axes object.

Raises:
-   TypeError if the annual and periodic arguments are not pandas Series.
-   TypeError if the annual index is not a PeriodIndex.
-   ValueError if the annual and periodic series do not have the same index.
def series_growth_plot(data: ~DataT, **kwargs) -> matplotlib.axes._axes.Axes:
292def series_growth_plot(
293    data: DataT,
294    **kwargs,
295) -> Axes:
296    """
297    Plot annual and periodic growth in percentage terms from
298    a pandas Series, and finalise the plot.
299
300    Args:
301    -   data: A pandas Series with an appropriate PeriodIndex.
302    -   kwargs:
303        -   takes the same kwargs as for growth_plot()
304    """
305
306    # --- check the kwargs
307    me = "series_growth_plot"
308    report_kwargs(called_from=me, **kwargs)
309    kwargs = validate_kwargs(SERIES_GROWTH_KW_TYPES, me, **kwargs)
310
311    # --- sanity checks
312    if not isinstance(data, Series):
313        raise TypeError(
314            "The data argument to series_growth_plot() must be a pandas Series"
315        )
316
317    # --- calculate growth and plot - add ylabel
318    ylabel: str | None = kwargs.pop("ylabel", None)
319    if ylabel is not None:
320        print(f"Did you intend to specify a value for the 'ylabel' in {me}()?")
321    ylabel = "Growth (%)" if ylabel is None else ylabel
322    growth = calc_growth(data)
323    ax = growth_plot(growth, **kwargs)
324    ax.set_ylabel(ylabel)
325    return ax

Plot annual and periodic growth in percentage terms from a pandas Series, and finalise the plot.

Args:

  • data: A pandas Series with an appropriate PeriodIndex.
  • kwargs:
    • takes the same kwargs as for growth_plot()
def multi_start( data: ~DataT, function: Union[Callable, list[Callable]], starts: Iterable[None | pandas._libs.tslibs.period.Period | int], **kwargs) -> None:
187def multi_start(
188    data: DataT,
189    function: Callable | list[Callable],
190    starts: Iterable[None | Period | int],
191    **kwargs,
192) -> None:
193    """
194    Create multiple plots with different starting points.
195    Each plot will start from the specified starting point.
196
197    Parameters
198    - data: Series | DataFrame - The data to be plotted.
199    - function: Callable | list[Callable] - The plotting function
200      to be used.
201    - starts: Iterable[Period | int | None] - The starting points
202      for each plot (None means use the entire data).
203    - **kwargs: Additional keyword arguments to be passed to
204      the plotting function.
205
206    Returns None.
207
208    Raises
209    - ValueError if the starts is not an iterable of None, Period or int.
210
211    Note: kwargs['tag'] is used to create a unique tag for each plot.
212    """
213
214    # --- sanity checks
215    me = "multi_start"
216    report_kwargs(called_from=me, **kwargs)
217    if not isinstance(starts, Iterable):
218        raise ValueError("starts must be an iterable of None, Period or int")
219    # data not checked here, assume it is checked by the called
220    # plot function.
221
222    # --- check the function argument
223    original_tag: Final[str] = kwargs.get("tag", "")
224    first, kwargs["function"] = first_unchain(function)
225    if not kwargs["function"]:
226        del kwargs["function"]  # remove the function key if it is empty
227
228    # --- iterate over the starts
229    for i, start in enumerate(starts):
230        kw = kwargs.copy()  # copy to avoid modifying the original kwargs
231        this_tag = f"{original_tag}_{i}"
232        kw["tag"] = this_tag
233        kw["plot_from"] = start  # rely on plotting function to constrain the data
234        first(data, **kw)

Create multiple plots with different starting points. Each plot will start from the specified starting point.

Parameters

  • data: Series | DataFrame - The data to be plotted.
  • function: Callable | list[Callable] - The plotting function to be used.
  • starts: Iterable[Period | int | None] - The starting points for each plot (None means use the entire data).
  • **kwargs: Additional keyword arguments to be passed to the plotting function.

Returns None.

Raises

  • ValueError if the starts is not an iterable of None, Period or int.

Note: kwargs['tag'] is used to create a unique tag for each plot.

def multi_column( data: pandas.core.frame.DataFrame, function: Union[Callable, list[Callable]], **kwargs) -> None:
237def multi_column(
238    data: DataFrame,
239    function: Callable | list[Callable],
240    **kwargs,
241) -> None:
242    """
243    Create multiple plots, one for each column in a DataFrame.
244    The plot title will be the column name.
245
246    Parameters
247    - data: DataFrame - The data to be plotted
248    - function: Callable - The plotting function to be used.
249    - **kwargs: Additional keyword arguments to be passed to
250      the plotting function.
251
252    Returns None.
253    """
254
255    # --- sanity checks
256    me = "multi_column"
257    report_kwargs(called_from=me, **kwargs)
258    if not isinstance(data, DataFrame):
259        raise TypeError("data must be a pandas DataFrame for multi_column()")
260    # Otherwise, the data is assumed to be checked by the called
261    # plot function, so we do not check it here.
262
263    # --- check the function argument
264    title_stem = kwargs.get("title", "")
265    tag: Final[str] = kwargs.get("tag", "")
266    first, kwargs["function"] = first_unchain(function)
267    if not kwargs["function"]:
268        del kwargs["function"]  # remove the function key if it is empty
269
270    # --- iterate over the columns
271    for i, col in enumerate(data.columns):
272
273        series = data[[col]]
274        kwargs["title"] = f"{title_stem}{col}" if title_stem else col
275
276        this_tag = f"_{tag}_{i}".replace("__", "_")
277        kwargs["tag"] = this_tag
278
279        first(series, **kwargs)

Create multiple plots, one for each column in a DataFrame. The plot title will be the column name.

Parameters

  • data: DataFrame - The data to be plotted
  • function: Callable - The plotting function to be used.
  • **kwargs: Additional keyword arguments to be passed to the plotting function.

Returns None.

def plot_then_finalise( data: ~DataT, function: Union[Callable, list[Callable]], **kwargs) -> None:
122def plot_then_finalise(
123    data: DataT,
124    function: Callable | list[Callable],
125    **kwargs,
126) -> None:
127    """
128    Chain a plotting function with the finalise_plot() function.
129    This is designed to be the last function in a chain.
130
131    Parameters
132    - data: Series | DataFrame - The data to be plotted.
133    - function: Callable | list[Callable] - The plotting function
134      to be used.
135    - **kwargs: Additional keyword arguments to be passed to
136      the plotting function, and then the finalise_plot() function.
137
138    Returns None.
139    """
140
141    # --- checks
142    me = "plot_then_finalise"
143    report_kwargs(called_from=me, **kwargs)
144    # validate once we have established the first function
145
146    # data is not checked here, assume it is checked by the called
147    # plot function.
148
149    first, kwargs["function"] = first_unchain(function)
150    if not kwargs["function"]:
151        del kwargs["function"]  # remove the function key if it is empty
152
153    bad_next = (multi_start, multi_column)
154    if first in bad_next:
155        # these functions should not be called by plot_then_finalise()
156        raise ValueError(
157            f"[{', '.join(k.__name__ for k in bad_next)}] should not be called by {me}. "
158            "Call them before calling {me}. "
159        )
160
161    if first in EXPECTED_CALLABLES:
162        expected = EXPECTED_CALLABLES[first]
163        plot_kwargs = limit_kwargs(expected, **kwargs)
164    else:
165        # this is an unexpected Callable, so we will give it a try
166        print(f"Unknown proposed function: {first}; nonetheless, will give it a try.")
167        expected = {}
168        plot_kwargs = kwargs.copy()
169
170    # --- validate the original kwargs (could not do before now)
171    kwargs = validate_kwargs(expected | FINALISE_KW_TYPES, me, **kwargs)
172
173    # --- call the first function with the data and selected plot kwargs
174    axes = first(data, **plot_kwargs)
175
176    # --- remove potentially overlapping kwargs
177    fp_kwargs = limit_kwargs(FINALISE_KW_TYPES, **kwargs)
178    overlapping = expected.keys() & FINALISE_KW_TYPES.keys()
179    if overlapping:
180        for key in overlapping:
181            fp_kwargs.pop(key, None)  # remove overlapping keys from kwargs
182
183    # --- finalise the plot
184    finalise_plot(axes, **fp_kwargs)

Chain a plotting function with the finalise_plot() function. This is designed to be the last function in a chain.

Parameters

  • data: Series | DataFrame - The data to be plotted.
  • function: Callable | list[Callable] - The plotting function to be used.
  • **kwargs: Additional keyword arguments to be passed to the plotting function, and then the finalise_plot() function.

Returns None.

def line_plot_finalise(data: ~DataT, **kwargs) -> None:
69def line_plot_finalise(
70    data: DataT,
71    **kwargs,
72) -> None:
73    """
74    A convenience function to call line_plot() then finalise_plot().
75    """
76    impose_legend(data=data, kwargs=kwargs)
77    plot_then_finalise(data, function=line_plot, **kwargs)

A convenience function to call line_plot() then finalise_plot().

def bar_plot_finalise(data: ~DataT, **kwargs) -> None:
80def bar_plot_finalise(
81    data: DataT,
82    **kwargs,
83) -> None:
84    """
85    A convenience function to call bar_plot() and finalise_plot().
86    """
87    impose_legend(data=data, kwargs=kwargs)
88    plot_then_finalise(
89        data,
90        function=bar_plot,
91        **kwargs,
92    )

A convenience function to call bar_plot() and finalise_plot().

def seastrend_plot_finalise(data: ~DataT, **kwargs) -> None:
 95def seastrend_plot_finalise(
 96    data: DataT,
 97    **kwargs,
 98) -> None:
 99    """
100    A convenience function to call seas_trend_plot() and finalise_plot().
101    """
102    impose_legend(force=True, kwargs=kwargs)
103    plot_then_finalise(data, function=seastrend_plot, **kwargs)

A convenience function to call seas_trend_plot() and finalise_plot().

def postcovid_plot_finalise(data: ~DataT, **kwargs) -> None:
106def postcovid_plot_finalise(
107    data: DataT,
108    **kwargs,
109) -> None:
110    """
111    A convenience function to call postcovid_plot() and finalise_plot().
112    """
113    impose_legend(force=True, kwargs=kwargs)
114    plot_then_finalise(data, function=postcovid_plot, **kwargs)

A convenience function to call postcovid_plot() and finalise_plot().

def revision_plot_finalise(data: ~DataT, **kwargs) -> None:
117def revision_plot_finalise(
118    data: DataT,
119    **kwargs,
120) -> None:
121    """
122    A convenience function to call revision_plot() and finalise_plot().
123    """
124    impose_legend(force=True, kwargs=kwargs)
125    plot_then_finalise(data=data, function=revision_plot, **kwargs)

A convenience function to call revision_plot() and finalise_plot().

def summary_plot_finalise(data: ~DataT, **kwargs) -> None:
156def summary_plot_finalise(
157    data: DataT,
158    **kwargs,
159) -> None:
160    """
161    A convenience function to call summary_plot() and finalise_plot().
162    This is more complex than most convienience methods.
163
164    Arguments
165    - data: DataFrame containing the summary data. The index must be a PeriodIndex.
166    - kwargs: additional arguments for the plot, including:
167        - plot_from: int | Period | None  (None means plot from 1995-01-01)
168        - verbose: if True, print the summary data.
169        - middle: proportion of data to highlight (default is 0.8).
170        - plot_type: list of plot types to generate (either "zscores" or "zscaled")
171          defaults to "zscores".
172    """
173
174    # --- standard arguments
175    kwargs[TITLE] = kwargs.get(TITLE, f"Summary at {data.index[-1]}")
176    kwargs[PRESERVE_LIMS] = kwargs.get(PRESERVE_LIMS, True)
177
178    start: None | int | Period = kwargs.get(PLOT_FROM, None)
179    if start is None:
180        start = data.index[0]
181    if isinstance(start, int):
182        start = data.index[start]
183    kwargs[PLOT_FROM] = start
184
185    for plot_type in (ZSCORES, ZSCALED):
186        # some sorting of kwargs for plot production
187        kwargs[PLOT_TYPE] = plot_type
188        kwargs[PRE_TAG] = plot_type  # necessary because the title is the same
189
190        if plot_type == "zscores":
191            kwargs[XLABEL] = f"Z-scores for prints since {start}"
192            kwargs[X0] = True
193        else:
194            kwargs[XLABEL] = f"-1 to 1 scaled z-scores since {start}"
195            kwargs.pop(X0, None)
196
197        plot_then_finalise(
198            data,
199            function=summary_plot,
200            **kwargs,
201        )

A convenience function to call summary_plot() and finalise_plot(). This is more complex than most convienience methods.

Arguments

  • data: DataFrame containing the summary data. The index must be a PeriodIndex.
  • kwargs: additional arguments for the plot, including:
    • plot_from: int | Period | None (None means plot from 1995-01-01)
    • verbose: if True, print the summary data.
    • middle: proportion of data to highlight (default is 0.8).
    • plot_type: list of plot types to generate (either "zscores" or "zscaled") defaults to "zscores".
def growth_plot_finalise(data: ~DataT, **kwargs) -> None:
146def growth_plot_finalise(data: DataT, **kwargs) -> None:
147    """
148    A convenience function to call series_growth_plot() and finalise_plot().
149    Use this when you are providing the raw growth data. Don't forget to
150    set the ylabel in kwargs.
151    """
152    impose_legend(force=True, kwargs=kwargs)
153    plot_then_finalise(data=data, function=growth_plot, **kwargs)

A convenience function to call series_growth_plot() and finalise_plot(). Use this when you are providing the raw growth data. Don't forget to set the ylabel in kwargs.

def series_growth_plot_finalise(data: ~DataT, **kwargs) -> None:
138def series_growth_plot_finalise(data: DataT, **kwargs) -> None:
139    """
140    A convenience function to call series_growth_plot() and finalise_plot().
141    """
142    impose_legend(force=True, kwargs=kwargs)
143    plot_then_finalise(data=data, function=series_growth_plot, **kwargs)

A convenience function to call series_growth_plot() and finalise_plot().

def run_plot_finalise(data: ~DataT, **kwargs) -> None:
128def run_plot_finalise(
129    data: DataT,
130    **kwargs,
131) -> None:
132    """
133    A convenience function to call run_plot() and finalise_plot().
134    """
135    plot_then_finalise(data=data, function=run_plot, **kwargs)

A convenience function to call run_plot() and finalise_plot().

FINALISE_KW_TYPES = {'title': (<class 'str'>, <class 'NoneType'>), 'xlabel': (<class 'str'>, <class 'NoneType'>), 'ylabel': (<class 'str'>, <class 'NoneType'>), 'ylim': (<class 'tuple'>, (<class 'float'>, <class 'int'>), <class 'NoneType'>), 'xlim': (<class 'tuple'>, (<class 'float'>, <class 'int'>), <class 'NoneType'>), 'yscale': (<class 'str'>, <class 'NoneType'>), 'xscale': (<class 'str'>, <class 'NoneType'>), 'legend': (<class 'dict'>, (<class 'str'>, (<class 'int'>, <class 'float'>, <class 'str'>)), <class 'bool'>, <class 'NoneType'>), 'axhspan': (<class 'dict'>, (<class 'str'>, (<class 'int'>, <class 'float'>, <class 'str'>)), <class 'NoneType'>), 'axvspan': (<class 'dict'>, (<class 'str'>, (<class 'int'>, <class 'float'>, <class 'str'>)), <class 'NoneType'>), 'axhline': (<class 'dict'>, (<class 'str'>, (<class 'int'>, <class 'float'>, <class 'str'>)), <class 'NoneType'>), 'axvline': (<class 'dict'>, (<class 'str'>, (<class 'int'>, <class 'float'>, <class 'str'>)), <class 'NoneType'>), 'pre_tag': <class 'str'>, 'tag': <class 'str'>, 'chart_dir': <class 'str'>, 'file_type': <class 'str'>, 'dpi': <class 'int'>, 'remove_legend': (<class 'NoneType'>, <class 'bool'>), 'preserve_lims': (<class 'NoneType'>, <class 'bool'>), 'figsize': (<class 'tuple'>, (<class 'float'>, <class 'int'>)), 'show': <class 'bool'>, 'lfooter': <class 'str'>, 'rfooter': <class 'str'>, 'lheader': <class 'str'>, 'rheader': <class 'str'>, 'zero_y': <class 'bool'>, 'y0': <class 'bool'>, 'x0': <class 'bool'>, 'dont_save': <class 'bool'>, 'dont_close': <class 'bool'>, 'concise_dates': <class 'bool'>}
BAR_KW_TYPES = {'ax': (<class 'matplotlib.axes._axes.Axes'>, <class 'NoneType'>), 'stacked': <class 'bool'>, 'max_ticks': <class 'int'>, 'plot_from': (<class 'int'>, <class 'pandas._libs.tslibs.period.Period'>, <class 'NoneType'>), 'color': (<class 'str'>, <class 'collections.abc.Sequence'>, (<class 'str'>,)), 'label_series': (<class 'bool'>, <class 'collections.abc.Sequence'>, (<class 'bool'>,)), 'width': (<class 'float'>, <class 'int'>), 'annotate': (<class 'NoneType'>, <class 'bool'>), 'fontsize': (<class 'int'>, <class 'float'>, <class 'str'>), 'fontname': <class 'str'>, 'rounding': <class 'int'>, 'rotation': (<class 'int'>, <class 'float'>), 'annotate_color': (<class 'str'>, <class 'NoneType'>), 'above': <class 'bool'>}
LINE_KW_TYPES = {'ax': (<class 'matplotlib.axes._axes.Axes'>, <class 'NoneType'>), 'style': (<class 'str'>, <class 'collections.abc.Sequence'>, (<class 'str'>,)), 'width': (<class 'float'>, <class 'int'>, <class 'collections.abc.Sequence'>, (<class 'float'>, <class 'int'>)), 'color': (<class 'str'>, <class 'collections.abc.Sequence'>, (<class 'str'>,)), 'alpha': (<class 'float'>, <class 'collections.abc.Sequence'>, (<class 'float'>,)), 'drawstyle': (<class 'str'>, <class 'collections.abc.Sequence'>, (<class 'str'>,), <class 'NoneType'>), 'marker': (<class 'str'>, <class 'collections.abc.Sequence'>, (<class 'str'>,), <class 'NoneType'>), 'markersize': (<class 'float'>, <class 'collections.abc.Sequence'>, (<class 'float'>,), <class 'int'>, <class 'NoneType'>), 'dropna': (<class 'bool'>, <class 'collections.abc.Sequence'>, (<class 'bool'>,)), 'annotate': (<class 'bool'>, <class 'collections.abc.Sequence'>, (<class 'bool'>,)), 'rounding': (<class 'collections.abc.Sequence'>, (<class 'bool'>, <class 'int'>), <class 'int'>, <class 'bool'>, <class 'NoneType'>), 'fontsize': (<class 'collections.abc.Sequence'>, (<class 'str'>, <class 'int'>, <class 'float'>), <class 'str'>, <class 'int'>, <class 'float'>), 'fontname': (<class 'str'>, <class 'collections.abc.Sequence'>, (<class 'str'>,)), 'rotation': (<class 'int'>, <class 'float'>, <class 'collections.abc.Sequence'>, (<class 'int'>, <class 'float'>)), 'annotate_color': (<class 'str'>, <class 'collections.abc.Sequence'>, (<class 'str'>,), <class 'bool'>, <class 'collections.abc.Sequence'>, (<class 'bool'>,), <class 'NoneType'>), 'plot_from': (<class 'int'>, <class 'pandas._libs.tslibs.period.Period'>, <class 'NoneType'>), 'label_series': (<class 'bool'>, <class 'collections.abc.Sequence'>, (<class 'bool'>,), <class 'NoneType'>)}
SEASTREND_KW_TYPES = {'ax': (<class 'matplotlib.axes._axes.Axes'>, <class 'NoneType'>), 'style': (<class 'str'>, <class 'collections.abc.Sequence'>, (<class 'str'>,)), 'width': (<class 'float'>, <class 'int'>, <class 'collections.abc.Sequence'>, (<class 'float'>, <class 'int'>)), 'color': (<class 'str'>, <class 'collections.abc.Sequence'>, (<class 'str'>,)), 'alpha': (<class 'float'>, <class 'collections.abc.Sequence'>, (<class 'float'>,)), 'drawstyle': (<class 'str'>, <class 'collections.abc.Sequence'>, (<class 'str'>,), <class 'NoneType'>), 'marker': (<class 'str'>, <class 'collections.abc.Sequence'>, (<class 'str'>,), <class 'NoneType'>), 'markersize': (<class 'float'>, <class 'collections.abc.Sequence'>, (<class 'float'>,), <class 'int'>, <class 'NoneType'>), 'dropna': (<class 'bool'>, <class 'collections.abc.Sequence'>, (<class 'bool'>,)), 'annotate': (<class 'bool'>, <class 'collections.abc.Sequence'>, (<class 'bool'>,)), 'rounding': (<class 'collections.abc.Sequence'>, (<class 'bool'>, <class 'int'>), <class 'int'>, <class 'bool'>, <class 'NoneType'>), 'fontsize': (<class 'collections.abc.Sequence'>, (<class 'str'>, <class 'int'>, <class 'float'>), <class 'str'>, <class 'int'>, <class 'float'>), 'fontname': (<class 'str'>, <class 'collections.abc.Sequence'>, (<class 'str'>,)), 'rotation': (<class 'int'>, <class 'float'>, <class 'collections.abc.Sequence'>, (<class 'int'>, <class 'float'>)), 'annotate_color': (<class 'str'>, <class 'collections.abc.Sequence'>, (<class 'str'>,), <class 'bool'>, <class 'collections.abc.Sequence'>, (<class 'bool'>,), <class 'NoneType'>), 'plot_from': (<class 'int'>, <class 'pandas._libs.tslibs.period.Period'>, <class 'NoneType'>), 'label_series': (<class 'bool'>, <class 'collections.abc.Sequence'>, (<class 'bool'>,), <class 'NoneType'>)}
POSTCOVID_KW_TYPES = {'start_r': <class 'pandas._libs.tslibs.period.Period'>, 'end_r': <class 'pandas._libs.tslibs.period.Period'>, 'width': (<class 'float'>, <class 'int'>, <class 'collections.abc.Sequence'>, (<class 'float'>, <class 'int'>)), 'style': (<class 'str'>, <class 'collections.abc.Sequence'>, (<class 'str'>,)), 'label_series': (<class 'bool'>, <class 'collections.abc.Sequence'>, (<class 'bool'>,), <class 'NoneType'>), 'annotate': (<class 'bool'>, <class 'collections.abc.Sequence'>, (<class 'bool'>,)), 'color': (<class 'str'>, <class 'collections.abc.Sequence'>, (<class 'str'>,)), 'ax': (<class 'matplotlib.axes._axes.Axes'>, <class 'NoneType'>), 'alpha': (<class 'float'>, <class 'collections.abc.Sequence'>, (<class 'float'>,)), 'drawstyle': (<class 'str'>, <class 'collections.abc.Sequence'>, (<class 'str'>,), <class 'NoneType'>), 'marker': (<class 'str'>, <class 'collections.abc.Sequence'>, (<class 'str'>,), <class 'NoneType'>), 'markersize': (<class 'float'>, <class 'collections.abc.Sequence'>, (<class 'float'>,), <class 'int'>, <class 'NoneType'>), 'dropna': (<class 'bool'>, <class 'collections.abc.Sequence'>, (<class 'bool'>,)), 'rounding': (<class 'collections.abc.Sequence'>, (<class 'bool'>, <class 'int'>), <class 'int'>, <class 'bool'>, <class 'NoneType'>), 'fontsize': (<class 'collections.abc.Sequence'>, (<class 'str'>, <class 'int'>, <class 'float'>), <class 'str'>, <class 'int'>, <class 'float'>), 'fontname': (<class 'str'>, <class 'collections.abc.Sequence'>, (<class 'str'>,)), 'rotation': (<class 'int'>, <class 'float'>, <class 'collections.abc.Sequence'>, (<class 'int'>, <class 'float'>)), 'annotate_color': (<class 'str'>, <class 'collections.abc.Sequence'>, (<class 'str'>,), <class 'bool'>, <class 'collections.abc.Sequence'>, (<class 'bool'>,), <class 'NoneType'>), 'plot_from': (<class 'int'>, <class 'pandas._libs.tslibs.period.Period'>, <class 'NoneType'>)}
REVISION_KW_TYPES = {'ax': (<class 'matplotlib.axes._axes.Axes'>, <class 'NoneType'>), 'style': (<class 'str'>, <class 'collections.abc.Sequence'>, (<class 'str'>,)), 'width': (<class 'float'>, <class 'int'>, <class 'collections.abc.Sequence'>, (<class 'float'>, <class 'int'>)), 'color': (<class 'str'>, <class 'collections.abc.Sequence'>, (<class 'str'>,)), 'alpha': (<class 'float'>, <class 'collections.abc.Sequence'>, (<class 'float'>,)), 'drawstyle': (<class 'str'>, <class 'collections.abc.Sequence'>, (<class 'str'>,), <class 'NoneType'>), 'marker': (<class 'str'>, <class 'collections.abc.Sequence'>, (<class 'str'>,), <class 'NoneType'>), 'markersize': (<class 'float'>, <class 'collections.abc.Sequence'>, (<class 'float'>,), <class 'int'>, <class 'NoneType'>), 'dropna': (<class 'bool'>, <class 'collections.abc.Sequence'>, (<class 'bool'>,)), 'annotate': (<class 'bool'>, <class 'collections.abc.Sequence'>, (<class 'bool'>,)), 'rounding': (<class 'collections.abc.Sequence'>, (<class 'bool'>, <class 'int'>), <class 'int'>, <class 'bool'>, <class 'NoneType'>), 'fontsize': (<class 'collections.abc.Sequence'>, (<class 'str'>, <class 'int'>, <class 'float'>), <class 'str'>, <class 'int'>, <class 'float'>), 'fontname': (<class 'str'>, <class 'collections.abc.Sequence'>, (<class 'str'>,)), 'rotation': (<class 'int'>, <class 'float'>, <class 'collections.abc.Sequence'>, (<class 'int'>, <class 'float'>)), 'annotate_color': (<class 'str'>, <class 'collections.abc.Sequence'>, (<class 'str'>,), <class 'bool'>, <class 'collections.abc.Sequence'>, (<class 'bool'>,), <class 'NoneType'>), 'plot_from': (<class 'int'>, <class 'pandas._libs.tslibs.period.Period'>, <class 'NoneType'>), 'label_series': (<class 'bool'>, <class 'collections.abc.Sequence'>, (<class 'bool'>,), <class 'NoneType'>)}
RUN_KW_TYPES = {'ax': (<class 'matplotlib.axes._axes.Axes'>, <class 'NoneType'>), 'style': (<class 'str'>, <class 'collections.abc.Sequence'>, (<class 'str'>,)), 'width': (<class 'float'>, <class 'int'>, <class 'collections.abc.Sequence'>, (<class 'float'>, <class 'int'>)), 'color': (<class 'str'>, <class 'collections.abc.Sequence'>, (<class 'str'>,)), 'alpha': (<class 'float'>, <class 'collections.abc.Sequence'>, (<class 'float'>,)), 'drawstyle': (<class 'str'>, <class 'collections.abc.Sequence'>, (<class 'str'>,), <class 'NoneType'>), 'marker': (<class 'str'>, <class 'collections.abc.Sequence'>, (<class 'str'>,), <class 'NoneType'>), 'markersize': (<class 'float'>, <class 'collections.abc.Sequence'>, (<class 'float'>,), <class 'int'>, <class 'NoneType'>), 'dropna': (<class 'bool'>, <class 'collections.abc.Sequence'>, (<class 'bool'>,)), 'annotate': (<class 'bool'>, <class 'collections.abc.Sequence'>, (<class 'bool'>,)), 'rounding': (<class 'collections.abc.Sequence'>, (<class 'bool'>, <class 'int'>), <class 'int'>, <class 'bool'>, <class 'NoneType'>), 'fontsize': (<class 'collections.abc.Sequence'>, (<class 'str'>, <class 'int'>, <class 'float'>), <class 'str'>, <class 'int'>, <class 'float'>), 'fontname': (<class 'str'>, <class 'collections.abc.Sequence'>, (<class 'str'>,)), 'rotation': (<class 'int'>, <class 'float'>, <class 'collections.abc.Sequence'>, (<class 'int'>, <class 'float'>)), 'annotate_color': (<class 'str'>, <class 'collections.abc.Sequence'>, (<class 'str'>,), <class 'bool'>, <class 'collections.abc.Sequence'>, (<class 'bool'>,), <class 'NoneType'>), 'plot_from': (<class 'int'>, <class 'pandas._libs.tslibs.period.Period'>, <class 'NoneType'>), 'label_series': (<class 'bool'>, <class 'collections.abc.Sequence'>, (<class 'bool'>,), <class 'NoneType'>), 'threshold': <class 'float'>, 'highlight': (<class 'str'>, <class 'collections.abc.Sequence'>, (<class 'str'>,)), 'direction': <class 'str'>}
SUMMARY_KW_TYPES = {'ax': (<class 'matplotlib.axes._axes.Axes'>, <class 'NoneType'>), 'verbose': <class 'bool'>, 'middle': <class 'float'>, 'plot_type': <class 'str'>, 'plot_from': (<class 'int'>, <class 'pandas._libs.tslibs.period.Period'>, <class 'NoneType'>)}
SERIES_GROWTH_KW_TYPES = {'ylabel': (<class 'str'>, <class 'NoneType'>), 'ax': (<class 'matplotlib.axes._axes.Axes'>, <class 'NoneType'>), 'plot_from': (<class 'int'>, <class 'pandas._libs.tslibs.period.Period'>, <class 'NoneType'>), 'label_series': <class 'bool'>, 'max_ticks': <class 'int'>, 'line_width': (<class 'float'>, <class 'int'>), 'line_color': <class 'str'>, 'line_style': <class 'str'>, 'annotate_line': <class 'bool'>, 'line_rounding': (<class 'bool'>, <class 'int'>), 'line_fontsize': (<class 'str'>, <class 'int'>, <class 'float'>), 'line_fontname': <class 'str'>, 'line_annotate_color': <class 'str'>, 'annotate_bars': (<class 'NoneType'>, <class 'bool'>), 'bar_fontsize': (<class 'str'>, <class 'int'>, <class 'float'>), 'bar_fontname': <class 'str'>, 'bar_rounding': <class 'int'>, 'bar_width': <class 'float'>, 'bar_color': <class 'str'>, 'bar_annotate_color': (<class 'str'>, <class 'NoneType'>), 'bar_rotation': (<class 'int'>, <class 'float'>), 'above': <class 'bool'>}
GROWTH_KW_TYPES = {'ax': (<class 'matplotlib.axes._axes.Axes'>, <class 'NoneType'>), 'plot_from': (<class 'int'>, <class 'pandas._libs.tslibs.period.Period'>, <class 'NoneType'>), 'label_series': <class 'bool'>, 'max_ticks': <class 'int'>, 'line_width': (<class 'float'>, <class 'int'>), 'line_color': <class 'str'>, 'line_style': <class 'str'>, 'annotate_line': <class 'bool'>, 'line_rounding': (<class 'bool'>, <class 'int'>), 'line_fontsize': (<class 'str'>, <class 'int'>, <class 'float'>), 'line_fontname': <class 'str'>, 'line_annotate_color': <class 'str'>, 'annotate_bars': (<class 'NoneType'>, <class 'bool'>), 'bar_fontsize': (<class 'str'>, <class 'int'>, <class 'float'>), 'bar_fontname': <class 'str'>, 'bar_rounding': <class 'int'>, 'bar_width': <class 'float'>, 'bar_color': <class 'str'>, 'bar_annotate_color': (<class 'str'>, <class 'NoneType'>), 'bar_rotation': (<class 'int'>, <class 'float'>), 'above': <class 'bool'>}