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    raw_growth_plot,
 25    series_growth_plot,
 26    SERIES_GROWTH_KW_TYPES,
 27    RAW_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    raw_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    "raw_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    "raw_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    "RAW_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:
279def finalise_plot(axes: Axes, **kwargs) -> None:
280    """
281    A function to finalise and save plots to the file system. The filename
282    for the saved plot is constructed from the global chart_dir, the plot's title,
283    any specified tag text, and the file_type for the plot.
284
285    Arguments:
286    - axes - matplotlib axes object - required
287    - kwargs
288        - title: str - plot title, also used to create the save file name
289        - xlabel: str | None - text label for the x-axis
290        - ylabel: str | None - label for the y-axis
291        - pre_tag: str - text before the title in file name
292        - tag: str - text after the title in the file name
293          (useful for ensuring that same titled charts do not over-write)
294        - chart_dir: str - location of the chart directory
295        - file_type: str - specify a file type - eg. 'png' or 'svg'
296        - lfooter: str - text to display on bottom left of plot
297        - rfooter: str - text to display of bottom right of plot
298        - lheader: str - text to display on top left of plot
299        - rheader: str - text to display of top right of plot
300        - figsize: tuple[float, float] - figure size in inches - eg. (8, 4)
301        - show: bool - whether to show the plot or not
302        - zero_y: bool - ensure y=0 is included in the plot.
303        - y0: bool - highlight the y=0 line on the plot (if in scope)
304        - x0: bool - highlights the x=0 line on the plot
305        - dont_save: bool - dont save the plot to the file system
306        - dont_close: bool - dont close the plot
307        - dpi: int - dots per inch for the saved chart
308        - legend: bool | dict - if dict, use as the arguments to pass to axes.legend(),
309          if True pass the global default arguments to axes.legend()
310        - axhspan: dict - arguments to pass to axes.axhspan()
311        - axvspan: dict - arguments to pass to axes.axvspan()
312        - axhline: dict - arguments to pass to axes.axhline()
313        - axvline: dict - arguments to pass to axes.axvline()
314        - ylim: tuple[float, float] - set lower and upper y-axis limits
315        - xlim: tuple[float, float] - set lower and upper x-axis limits
316        - preserve_lims: bool - if True, preserve the original axes limits,
317          lims saved at the start, and restored after the tight layout
318        - remove_legend: bool | None - if True, remove the legend from the plot
319        - report_kwargs: bool - if True, report the kwargs used in this function
320
321     Returns:
322        - None
323    """
324
325    # --- check the kwargs
326    me = "finalise_plot"
327    report_kwargs(called_from=me, **kwargs)
328    validate_kwargs(FINALISE_KW_TYPES, me, **kwargs)
329
330    # --- sanity checks
331    if len(axes.get_children()) < 1:
332        print("Warning: finalise_plot() called with empty axes, which was ignored.")
333        return
334
335    # --- remember should we need to restore the axes limits
336    xlim, ylim = axes.get_xlim(), axes.get_ylim()
337
338    # margins
339    # axes.use_sticky_margins = False   ### CHECK THIS
340    axes.margins(0.02)
341    axes.autoscale(tight=False)  # This is problematic ...
342
343    _apply_kwargs(axes, **kwargs)
344
345    # tight layout and save the figure
346    fig = axes.figure
347    if not isinstance(fig, mpl.figure.SubFigure):  # should never be a SubFigure
348        fig.tight_layout(pad=1.1)
349        if "preserve_lims" in kwargs and kwargs["preserve_lims"]:
350            # restore the original limits of the axes
351            axes.set_xlim(xlim)
352            axes.set_ylim(ylim)
353        _apply_late_kwargs(axes, **kwargs)
354        legend = axes.get_legend()
355        if legend and kwargs.get("remove_legend", False):
356            legend.remove()
357        _save_to_file(fig, **kwargs)
358
359    # show the plot in Jupyter Lab
360    if "show" in kwargs and kwargs["show"]:
361        plt.show()
362
363    # And close
364    closing = True if "dont_close" not in kwargs else not kwargs["dont_close"]
365    if closing:
366        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:
 94def line_plot(data: DataT, **kwargs) -> plt.Axes:
 95    """
 96    Build a single plot from the data passed in.
 97    This can be a single- or multiple-line plot.
 98    Return the axes object for the build.
 99
100    Agruments:
101    - data: DataFrame | Series - data to plot
102    - kwargs:
103        - ax: plt.Axes | None - axes to plot on (optional)
104        - dropna: bool | list[bool] - whether to delete NAs frm the
105          data before plotting [optional]
106        - color: str | list[str] - line colors.
107        - width: float | list[float] - line widths [optional].
108        - style: str | list[str] - line styles [optional].
109        - alpha: float | list[float] - line transparencies [optional].
110        - marker: str | list[str] - line markers [optional].
111        - marker_size: float | list[float] - line marker sizes [optional].
112        - annotate: bool | list[bool] - whether to annotate a series.
113        - rounding: int | bool | list[int | bool] - number of decimal places
114          to round an annotation. If True, a default between 0 and 2 is
115          used.
116        - fontsize: int | str | list[int | str] - font size for the
117          annotation.
118        - drawstyle: str | list[str] - matplotlib line draw styles.
119
120    Returns:
121    - axes: plt.Axes - the axes object for the plot
122    """
123
124    # --- check the kwargs
125    me = "line_plot"
126    report_kwargs(called_from=me, **kwargs)
127    validate_kwargs(LINE_KW_TYPES, me, **kwargs)
128
129    # --- check the data
130    data = check_clean_timeseries(data, me)
131    df = DataFrame(data)  # really we are only plotting DataFrames
132    df, kwargs = constrain_data(df, **kwargs)
133
134    # --- some special defaults
135    if len(df.columns) > 1:
136        # default to displaying a legend
137        kwargs["legend"] = kwargs.get("legend", True)
138    if len(df.columns) > 4:
139        # default to using a style for the lines
140        kwargs["style"] = kwargs.get("style", ["solid", "dashed", "dashdot", "dotted"])
141
142    # --- Let's plot
143    axes, kwargs = get_axes(**kwargs)  # get the axes to plot on
144    if df.empty or df.isna().all().all():
145        # Note: finalise plot will ignore an empty axes object
146        print("Warning: No data to plot.")
147        return axes
148
149    # --- get the arguments for each line we will plot ...
150    item_count = len(df.columns)
151    num_data_points = len(df)
152    swce, kwargs = _get_style_width_color_etc(item_count, num_data_points, **kwargs)
153
154    for i, column in enumerate(df.columns):
155        series = df[column]
156        series = series.dropna() if DROPNA in swce and swce[DROPNA][i] else series
157        if series.empty or series.isna().all():
158            continue
159
160        axes = series.plot(
161            ls=swce[STYLE][i],
162            lw=swce[WIDTH][i],
163            color=swce[COLOR][i],
164            alpha=swce[ALPHA][i],
165            marker=swce[MARKER][i],
166            ms=swce[MARKERSIZE][i],
167            drawstyle=swce[DRAWSTYLE][i],
168            ax=axes,
169        )
170
171        if swce[ANNOTATE][i] is None or not swce[ANNOTATE][i]:
172            continue
173
174        annotate_series(
175            series,
176            axes,
177            rounding=swce[ROUNDING][i],
178            color=swce[COLOR][i],
179            fontsize=swce[FONTSIZE][i],
180        )
181
182    # add a legend if requested
183    if len(df.columns) > 1:
184        kwargs[LEGEND] = kwargs.get(LEGEND, get_setting("legend"))
185
186    if LEGEND in kwargs:
187        make_legend(axes, kwargs[LEGEND])
188
189    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:
    • ax: plt.Axes | None - axes to plot on (optional)
    • 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].
    • 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.
    • drawstyle: str | list[str] - matplotlib line draw styles.

Returns:

  • axes: plt.Axes - the axes object for the plot
def bar_plot(data: ~DataT, **kwargs) -> matplotlib.axes._axes.Axes:
 43def bar_plot(
 44    data: DataT,
 45    **kwargs,
 46) -> Axes:
 47    """
 48    Create a bar plot from the given data. Each column in the DataFrame
 49    will be stacked on top of each other, with positive values above
 50    zero and negative values below zero.
 51
 52    Parameters
 53    - data: Series - The data to plot. Can be a DataFrame or a Series.
 54    - **kwargs: dict Additional keyword arguments for customization.
 55        - color: list - A list of colors for the each series (column) in  the DataFrame.
 56        - width: float - The width of the bars.
 57        - stacked: bool - If True, the bars will be stacked.
 58        - rotation: int - The rotation angle in degrees for the x-axis labels.
 59        - bar_legend: bool - If True, show the legend. Defaults to True
 60          if more than one bar being plotted for each category.
 61        - "max_ticks": int - The maximum number of ticks on the x-axis,
 62          (this option only applies to PeriodIndex data.).
 63
 64    Note: This function does not assume all data is timeseries with a PeriodIndex,
 65
 66    Returns
 67    - axes: Axes - The axes for the plot.
 68    """
 69
 70    # --- check the kwargs
 71    me = "bar_plot"
 72    report_kwargs(called_from=me, **kwargs)
 73    validate_kwargs(BAR_KW_TYPES, me, **kwargs)
 74
 75    # --- get the data
 76    # no call to check_clean_timeseries here, as bar plots are not
 77    # necessarily timeseries data. If the data is a Series, it will be
 78    # converted to a DataFrame with a single column.
 79    df = DataFrame(data)  # really we are only plotting DataFrames
 80    df, kwargs = constrain_data(df, **kwargs)
 81    item_count = len(df.columns)
 82
 83    defaults: dict[str, Any] = {
 84        "color": get_color_list(item_count),
 85        "width": get_setting("bar_width"),
 86        "stacked": False,
 87        "rotation": 90,
 88        "bar_legend": (item_count > 1),
 89        "max_ticks": 10,
 90    }
 91    bar_args, remaining_kwargs = apply_defaults(item_count, defaults, kwargs)
 92
 93    # --- plot the data
 94    axes, _rkwargs = get_axes(**remaining_kwargs)
 95
 96    df.plot.bar(
 97        ax=axes,
 98        color=bar_args["color"],
 99        stacked=bar_args["stacked"][0],
100        width=bar_args["width"][0],
101        legend=bar_args["bar_legend"][0],
102    )
103
104    rotate_labels = True
105    if isinstance(df.index, PeriodIndex):
106        complete = period_range(
107            start=df.index.min(), end=df.index.max(), freq=df.index.freqstr
108        )
109        if complete.equals(df.index):
110            # if the index is complete, we can set the labels
111            set_labels(axes, df.index, bar_args["max_ticks"][0])
112            rotate_labels = False
113
114    if rotate_labels:
115        plt.xticks(rotation=bar_args["rotation"][0])
116
117    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.
    • color: list - A list of colors for the each series (column) in the DataFrame.
    • width: float - The width of the bars.
    • stacked: bool - If True, the bars will be stacked.
    • rotation: int - The rotation angle in degrees for the x-axis labels.
    • bar_legend: bool - If True, show the legend. Defaults to True if more than one bar being plotted for each category.
    • "max_ticks": int - The maximum number of ticks on the x-axis, (this option only applies to PeriodIndex data.).

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

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

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:
29def revision_plot(data: DataT, **kwargs) -> Axes:
30    """
31    Plot the revisions to ABS data.
32
33    Arguments
34    data: pd.DataFrame - the data to plot, the DataFrame has a
35        column for each data revision
36    recent: int - the number of recent data points to plot
37    kwargs : dict :
38        -   units: str - the units for the data (Note: you may need to
39            recalibrate the units for the y-axis)
40        -   rounding: int | bool - if True apply default rounding, otherwise
41            apply int rounding.
42    """
43
44    # --- check the kwargs and data
45    me = "revision_plot"
46    report_kwargs(called_from=me, **kwargs)
47    validate_kwargs(REVISION_KW_TYPES, me, **kwargs)
48
49    data = check_clean_timeseries(data, me)
50
51    # --- critical defaults
52    kwargs["plot_from"] = kwargs.get("plot_from", -19)
53
54    # --- plot
55    axes = line_plot(data, **kwargs)
56
57    # --- Annotate the last value in each series ...
58    rounding: int | bool = kwargs.pop(ROUNDING, True)
59    for c in data.columns:
60        col: Series = data.loc[:, c].dropna()
61        annotate_series(col, axes, color="#222222", rounding=rounding, fontsize="small")
62
63    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 recent: int - the number of recent data points to plot kwargs : dict : - units: str - the units for the data (Note: you may need to recalibrate the units for the y-axis) - rounding: int | bool - if True apply default rounding, otherwise apply int rounding.

def run_plot(data: ~DataT, **kwargs) -> matplotlib.axes._axes.Axes:
115def run_plot(data: DataT, **kwargs) -> Axes:
116    """Plot a series of percentage rates, highlighting the increasing runs.
117
118    Arguments
119     - data - ordered pandas Series of percentages, with PeriodIndex
120     - **kwargs
121        - threshold - float - used to ignore micro noise near zero
122          (for example, threshhold=0.01)
123        - round - int - rounding for highlight text
124        - highlight - str or Sequence[str] - color(s) for highlighting the
125          runs, two colors can be specified in a list if direction is "both"
126        - direction - str - whether the highlight is for an upward
127          or downward or both runs. Options are "up", "down" or "both".
128        - in addition the **kwargs for line_plot are accepted.
129
130    Return
131     - matplotlib Axes object"""
132
133    # --- check the kwargs
134    me = "run_plot"
135    report_kwargs(called_from=me, **kwargs)
136    validate_kwargs(RUN_KW_TYPES, me, **kwargs)
137
138    # --- check the data
139    series = check_clean_timeseries(data, me)
140    if not isinstance(series, Series):
141        raise TypeError("series must be a pandas Series for run_plot()")
142    series, kwargs = constrain_data(series, **kwargs)
143
144    # --- default arguments - in **kwargs
145    kwargs[THRESHOLD] = kwargs.get(THRESHOLD, 0.1)
146    kwargs[ROUND] = kwargs.get(ROUND, 2)
147    direct = kwargs[DIRECTION] = kwargs.get(DIRECTION, "up")
148    kwargs[HIGHLIGHT], kwargs["color"] = (
149        (kwargs.get(HIGHLIGHT, "gold"), kwargs.get("color", "#dd0000"))
150        if direct == "up"
151        else (
152            (kwargs.get(HIGHLIGHT, "skyblue"), kwargs.get("color", "navy"))
153            if direct == "down"
154            else (
155                kwargs.get(HIGHLIGHT, ("gold", "skyblue")),
156                kwargs.get("color", "navy"),
157            )
158        )
159    )
160
161    # defauls for line_plot
162    kwargs["width"] = kwargs.get("width", 2)
163
164    # plot the line
165    kwargs["drawstyle"] = kwargs.get("drawstyle", "steps-post")
166    lp_kwargs = limit_kwargs(LINE_KW_TYPES, **kwargs)
167    axes = line_plot(series, **lp_kwargs)
168
169    # plot the runs
170    match kwargs[DIRECTION]:
171        case "up":
172            _plot_runs(axes, series, up=True, **kwargs)
173        case "down":
174            _plot_runs(axes, series, up=False, **kwargs)
175        case "both":
176            _plot_runs(axes, series, up=True, **kwargs)
177            _plot_runs(axes, series, up=False, **kwargs)
178        case _:
179            raise ValueError(
180                f"Invalid value for direction: {kwargs[DIRECTION]}. "
181                "Expected 'up', 'down', or 'both'."
182            )
183    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:
200def summary_plot(
201    data: DataT,  # summary data
202    **kwargs,
203) -> Axes:
204    """Plot a summary of historical data for a given DataFrame.
205
206    Args:
207    - summary: DataFrame containing the summary data. The column names are
208      used as labels for the plot.
209    - kwargs: additional arguments for the plot, including:
210        - plot_from: int | Period | None
211        - verbose: if True, print the summary data.
212        - middle: proportion of data to highlight (default is 0.8).
213        - plot_types: list of plot types to generate.
214
215
216    Returns Axes.
217    """
218
219    # --- check the kwargs
220    me = "summary_plot"
221    report_kwargs(called_from=me, **kwargs)
222    validate_kwargs(SUMMARY_KW_TYPES, me, **kwargs)
223
224    # --- check the data
225    data = check_clean_timeseries(data, me)
226    if not isinstance(data, DataFrame):
227        raise TypeError("data must be a pandas DataFrame for summary_plot()")
228    df = DataFrame(data)  # syntactic sugar for type hinting
229
230    # --- optional arguments
231    verbose = kwargs.pop("verbose", False)
232    middle = float(kwargs.pop("middle", 0.8))
233    plot_type = kwargs.pop("plot_type", ZSCORES)
234    kwargs["legend"] = kwargs.get(
235        "legend",
236        {
237            # put the legend below the x-axis label
238            "loc": "upper center",
239            "fontsize": "xx-small",
240            "bbox_to_anchor": (0.5, -0.125),
241            "ncol": 4,
242        },
243    )
244
245    # get the data, calculate z-scores and scaled scores based on the start period
246    subset, kwargs = constrain_data(df, **kwargs)
247    z_scores, z_scaled = _calculate_z(subset, middle, verbose=verbose)
248
249    # plot as required by the plot_types argument
250    adjusted = z_scores if plot_type == ZSCORES else z_scaled
251    ax = _horizontal_bar_plot(subset, adjusted, middle, plot_type, kwargs)
252    ax.tick_params(axis="y", labelsize="small")
253    make_legend(ax, kwargs["legend"])
254    ax.set_xlim(kwargs.get("xlim", None))  # provide space for the labels
255
256    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:
 63def calc_growth(series: Series) -> DataFrame:
 64    """
 65    Calculate annual and periodic growth for a pandas Series,
 66    where the index is a PeriodIndex.
 67
 68    Args:
 69    -   series: A pandas Series with an appropriate PeriodIndex.
 70
 71    Returns a two column DataFrame:
 72
 73    Raises
 74    -   TypeError if the series is not a pandas Series.
 75    -   TypeError if the series index is not a PeriodIndex.
 76    -   ValueError if the series is empty.
 77    -   ValueError if the series index does not have a frequency of Q, M, or D.
 78    -   ValueError if the series index has duplicates.
 79    """
 80
 81    # --- sanity checks
 82    if not isinstance(series, Series):
 83        raise TypeError("The series argument must be a pandas Series")
 84    if not isinstance(series.index, PeriodIndex):
 85        raise TypeError("The series index must be a pandas PeriodIndex")
 86    if series.empty:
 87        raise ValueError("The series argument must not be empty")
 88    if series.index.freqstr[0] not in ("Q", "M", "D"):
 89        raise ValueError("The series index must have a frequency of Q, M, or D")
 90    if series.index.has_duplicates:
 91        raise ValueError("The series index must not have duplicate values")
 92
 93    # --- ensure the index is complete and the date is sorted
 94    complete = period_range(start=series.index.min(), end=series.index.max())
 95    series = series.reindex(complete, fill_value=nan)
 96    series = series.sort_index(ascending=True)
 97
 98    # --- calculate annual and periodic growth
 99    ppy = {"Q": 4, "M": 12, "D": 365}[PeriodIndex(series.index).freqstr[:1]]
100    annual = series.pct_change(periods=ppy) * 100
101    periodic = series.pct_change(periods=1) * 100
102    periodic_name = {4: "Quarterly", 12: "Monthly", 365: "Daily"}[ppy] + " Growth"
103    return DataFrame(
104        {
105            "Annual Growth": annual,
106            periodic_name: periodic,
107        }
108    )

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 raw_growth_plot(data: ~DataT, **kwargs) -> matplotlib.axes._axes.Axes:
160def raw_growth_plot(
161    data: DataT,
162    **kwargs,
163) -> Axes:
164    """
165    Plot annual (as a line) and periodic (as bars) growth on the
166    same axes.
167
168    Args:
169    -   data: A pandas DataFrame with two columns:
170    -   kwargs:
171        -   line_width: The width of the line (default is 2).
172        -   line_color: The color of the line (default is "darkblue").
173        -   line_style: The style of the line (default is "-").
174        -   annotate_line: None | bool | int | str - fontsize to annotate
175            the line (default is "small", which means the line is annotated with
176            small text).
177        -   rounding: None | bool | int - the number of decimal places to round
178            the line (default is 0).
179        -   bar_width: The width of the bars (default is 0.8).
180        -   bar_color: The color of the bars (default is "indianred").
181        -   annotate_bar: None | int | str - fontsize to annotate the bars
182            (default is "small", which means the bars are annotated with
183            small text).
184        -   bar_rounding: The number of decimal places to round the
185            annotations to (default is 1).
186        -   plot_from: None | Period | int -- if:
187            -   None: the entire series is plotted
188            -   Period: the plot starts from this period
189            -   int: the plot starts from this +/- index position
190        -   max_ticks: The maximum number of ticks to show on the x-axis
191            (default is 10).
192
193    Returns:
194    -   axes: The matplotlib Axes object.
195
196    Raises:
197    -   TypeError if the annual and periodic arguments are not pandas Series.
198    -   TypeError if the annual index is not a PeriodIndex.
199    -   ValueError if the annual and periodic series do not have the same index.
200    """
201
202    # --- check the kwargs
203    me = "raw_growth_plot"
204    report_kwargs(called_from=me, **kwargs)
205    validate_kwargs(RAW_GROWTH_KW_TYPES, me, **kwargs)
206
207    # --- data checks
208    data = check_clean_timeseries(data, me)
209    if len(data.columns) != 2:
210        raise TypeError("The data argument must be a pandas DataFrame with two columns")
211
212    # --- get the series of interest ...
213    annual = data[data.columns[0]]
214    periodic = data[data.columns[1]]
215
216    # --- plot
217    plot_from: None | Period | int = kwargs.get("plot_from", None)
218    if plot_from is not None:
219        if isinstance(plot_from, int):
220            plot_from = annual.index[plot_from]
221        annual = annual[annual.index >= plot_from]
222        periodic = periodic[periodic.index >= plot_from]
223
224    save_index = PeriodIndex(annual.index).copy()
225    annual.index = Index(range(len(annual)))
226    annual.name = "Annual Growth"
227    periodic.index = annual.index
228    periodic.name = {"M": "Monthly", "Q": "Quarterly", "D": "Daily"}[
229        PeriodIndex(save_index).freqstr[:1]
230    ] + " Growth"
231    color = kwargs.get("bar_color", "#dd0000")
232    kwargs["bar_color"] = color  # for annotations
233    axes = periodic.plot.bar(
234        color=color,
235        width=kwargs.get("bar_width}", 0.8),
236    )
237    thin_threshold = 180
238    annual.plot(
239        ax=axes,
240        color=kwargs.get("line_color", "darkblue"),
241        lw=kwargs.get(
242            "line_width",
243            (
244                get_setting("line_normal")
245                if len(annual) >= thin_threshold
246                else get_setting("line_wide")
247            ),
248        ),
249        linestyle=kwargs.get("line_style", "-"),
250    )
251    _annotations(annual, periodic, axes, **kwargs)
252
253    # --- expose the legend by default
254    legend = kwargs.get("legend", True)
255    make_legend(axes, legend)
256
257    # --- fix the x-axis labels
258    set_labels(axes, save_index, kwargs.get("max_ticks", 10))
259
260    # --- and done ...
261    return axes

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

Args:

  • data: A pandas DataFrame with two columns:
  • kwargs:
    • line_width: The width of the line (default is 2).
    • line_color: The color of the line (default is "darkblue").
    • line_style: The style of the line (default is "-").
    • annotate_line: None | bool | int | str - fontsize to annotate the line (default is "small", which means the line is annotated with small text).
    • rounding: None | bool | int - the number of decimal places to round the line (default is 0).
    • bar_width: The width of the bars (default is 0.8).
    • bar_color: The color of the bars (default is "indianred").
    • annotate_bar: None | int | str - fontsize to annotate the bars (default is "small", which means the bars are annotated with small text).
    • bar_rounding: The number of decimal places to round the annotations to (default is 1).
    • plot_from: None | Period | int -- if:
      • None: the entire series is plotted
      • Period: the plot starts from this period
      • int: the plot starts from this +/- index position
    • max_ticks: The maximum number of ticks to show on the x-axis (default is 10).

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:
264def series_growth_plot(
265    data: DataT,
266    **kwargs,
267) -> Axes:
268    """
269    Plot annual and periodic growth from a pandas Series,
270    and finalise the plot.
271
272    Args:
273    -   data: A pandas Series with an appropriate PeriodIndex.
274    -   kwargs:
275        -   takes the same kwargs as for growth_plot()
276    """
277
278    # --- check the kwargs
279    me = "series_growth_plot"
280    report_kwargs(called_from=me, **kwargs)
281    validate_kwargs(SERIES_GROWTH_KW_TYPES, me, **kwargs)
282
283    # --- sanity checks
284    if not isinstance(data, Series):
285        raise TypeError(
286            "The data argument to series_growth_plot() must be a pandas Series"
287        )
288
289    # --- calculate growth and plot - add ylabel
290    ylabel: str | None = kwargs.pop("ylabel", None)
291    if ylabel is not None:
292        print(f"Did you intend to specify a value for the 'ylabel' in {me}()?")
293    ylabel = "Growth (%)" if ylabel is None else ylabel
294    growth = calc_growth(data)
295    ax = raw_growth_plot(growth, **kwargs)
296    ax.set_ylabel(ylabel)
297    return ax

Plot annual and periodic growth 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:
188def multi_start(
189    data: DataT,
190    function: Callable | list[Callable],
191    starts: Iterable[None | Period | int],
192    **kwargs,
193) -> None:
194    """
195    Create multiple plots with different starting points.
196    Each plot will start from the specified starting point.
197
198    Parameters
199    - data: Series | DataFrame - The data to be plotted.
200    - function: Callable | list[Callable] - The plotting function
201      to be used.
202    - starts: Iterable[Period | int | None] - The starting points
203      for each plot (None means use the entire data).
204    - **kwargs: Additional keyword arguments to be passed to
205      the plotting function.
206
207    Returns None.
208
209    Raises
210    - ValueError if the starts is not an iterable of None, Period or int.
211
212    Note: kwargs['tag'] is used to create a unique tag for each plot.
213    """
214
215    # --- sanity checks
216    me = "multi_start"
217    report_kwargs(called_from=me, **kwargs)
218    if not isinstance(starts, Iterable):
219        raise ValueError("starts must be an iterable of None, Period or int")
220    # data not checked here, assume it is checked by the called
221    # plot function.
222
223    # --- check the function argument
224    original_tag: Final[str] = kwargs.get("tag", "")
225    first, kwargs["function"] = first_unchain(function)
226    if not kwargs["function"]:
227        del kwargs["function"]  # remove the function key if it is empty
228
229    # --- iterate over the starts
230    for i, start in enumerate(starts):
231        kw = kwargs.copy()  # copy to avoid modifying the original kwargs
232        this_tag = f"{original_tag}_{i}"
233        kw["tag"] = this_tag
234        kw["plot_from"] = start  # rely on plotting function to constrain the data
235        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:
238def multi_column(
239    data: DataFrame,
240    function: Callable | list[Callable],
241    **kwargs,
242) -> None:
243    """
244    Create multiple plots, one for each column in a DataFrame.
245    The plot title will be the column name.
246
247    Parameters
248    - data: DataFrame - The data to be plotted
249    - function: Callable - The plotting function to be used.
250    - **kwargs: Additional keyword arguments to be passed to
251      the plotting function.
252
253    Returns None.
254    """
255
256    # --- sanity checks
257    me = "multi_column"
258    report_kwargs(called_from=me, **kwargs)
259    if not isinstance(data, DataFrame):
260        raise TypeError("data must be a pandas DataFrame for multi_column()")
261    # Otherwise, the data is assumed to be checked by the called
262    # plot function, so we do not check it here.
263
264    # --- check the function argument
265    title_stem = kwargs.get("title", "")
266    tag: Final[str] = kwargs.get("tag", "")
267    first, kwargs["function"] = first_unchain(function)
268    if not kwargs["function"]:
269        del kwargs["function"]  # remove the function key if it is empty
270
271    # --- iterate over the columns
272    for i, col in enumerate(data.columns):
273
274        series = data[[col]]
275        kwargs["title"] = f"{title_stem}{col}" if title_stem else col
276
277        this_tag = f"_{tag}_{i}".replace("__", "_")
278        kwargs["tag"] = this_tag
279
280        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:
123def plot_then_finalise(
124    data: DataT,
125    function: Callable | list[Callable],
126    **kwargs,
127) -> None:
128    """
129    Chain a plotting function with the finalise_plot() function.
130    This is designed to be the last function in a chain.
131
132    Parameters
133    - data: Series | DataFrame - The data to be plotted.
134    - function: Callable | list[Callable] - The plotting function
135      to be used.
136    - **kwargs: Additional keyword arguments to be passed to
137      the plotting function, and then the finalise_plot() function.
138
139    Returns None.
140    """
141
142    # --- checks
143    me = "plot_then_finalise"
144    report_kwargs(called_from=me, **kwargs)
145    # validate once we have established the first function
146
147    # data is not checked here, assume it is checked by the called
148    # plot function.
149
150    first, kwargs["function"] = first_unchain(function)
151    if not kwargs["function"]:
152        del kwargs["function"]  # remove the function key if it is empty
153
154    bad_next = (multi_start, multi_column)
155    if first in bad_next:
156        # these functions should not be called by plot_then_finalise()
157        raise ValueError(
158            f"[{', '.join(k.__name__ for k in bad_next)}] should not be called by {me}. "
159            "Call them before calling {me}. "
160        )
161
162    if first in EXPECTED_CALLABLES:
163        expected = EXPECTED_CALLABLES[first]
164        plot_kwargs = limit_kwargs(expected, **kwargs)
165    else:
166        # this is an unexpected Callable, so we will give it a try
167        print(f"Unknown proposed function: {first}; nonetheless, will give it a try.")
168        expected = {}
169        plot_kwargs = kwargs.copy()
170
171    # --- validate the original kwargs (could not do before now)
172    validate_kwargs(FINALISE_KW_TYPES | expected, me, **kwargs)
173
174    # --- call the first function with the data and selected plot kwargs
175    axes = first(data, **plot_kwargs)
176
177    # --- remove potentially overlapping kwargs
178    fp_kwargs = limit_kwargs(FINALISE_KW_TYPES, **kwargs)
179    overlapping = expected.keys() & FINALISE_KW_TYPES.keys()
180    if overlapping:
181        for key in overlapping:
182            fp_kwargs.pop(key, None)  # remove overlapping keys from kwargs
183
184    # --- finalise the plot
185    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:
41def line_plot_finalise(
42    data: DataT,
43    **kwargs,
44) -> None:
45    """
46    A convenience function to call plot_then_finalise(), which
47    wraps calls to line_plot() and finalise_plot().
48    """
49
50    plot_then_finalise(
51        data,
52        function=line_plot,
53        **kwargs,
54    )

A convenience function to call plot_then_finalise(), which wraps calls to line_plot() and finalise_plot().

def bar_plot_finalise(data: ~DataT, **kwargs) -> None:
57def bar_plot_finalise(
58    data: DataT,
59    **kwargs,
60) -> None:
61    """
62    A convenience function to call plot_then_finalise(), which
63    wraps calls to bar_plot() and finalise_plot().
64    """
65
66    plot_then_finalise(
67        data,
68        function=bar_plot,
69        **kwargs,
70    )

A convenience function to call plot_then_finalise(), which wraps calls to bar_plot() and finalise_plot().

def seastrend_plot_finalise(data: ~DataT, **kwargs) -> None:
73def seastrend_plot_finalise(
74    data: DataT,
75    **kwargs,
76) -> None:
77    """
78    A convenience function to call seas_trend_plot() and finalise_plot().
79    """
80
81    plot_then_finalise(
82        data,
83        function=seastrend_plot,
84        **kwargs,
85    )

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

def postcovid_plot_finalise(data: ~DataT, **kwargs) -> None:
 88def postcovid_plot_finalise(
 89    data: DataT,
 90    **kwargs,
 91) -> None:
 92    """
 93    A convenience function to call postcovid_plot() and finalise_plot().
 94    """
 95
 96    plot_then_finalise(
 97        data,
 98        function=postcovid_plot,
 99        **kwargs,
100    )

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

def revision_plot_finalise(data: ~DataT, **kwargs) -> None:
103def revision_plot_finalise(
104    data: DataT,
105    **kwargs,
106) -> None:
107    """
108    A convenience function to call revision_plot() and finalise_plot().
109    """
110
111    kwargs["legend"] = kwargs.get(
112        "legend", {"loc": "best", "fontsize": "x-small", "ncol": 2}
113    )
114    kwargs["style"] = kwargs.get("style", ["solid", "dashed", "dashdot", "dotted"])
115    plot_then_finalise(
116        data,
117        function=revision_plot,
118        **kwargs,
119    )

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

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

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 raw_growth_plot_finalise(data: ~DataT, **kwargs) -> None:
151def raw_growth_plot_finalise(data: DataT, **kwargs) -> None:
152    """
153    A convenience function to call series_growth_plot() and finalise_plot().
154    Use this when you are providing the raw growth data. Don't forget to
155    set the ylabel in kwargs.
156    """
157
158    plot_then_finalise(
159        data=data,
160        function=raw_growth_plot,
161        **kwargs,
162    )

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:
137def series_growth_plot_finalise(data: DataT, **kwargs) -> None:
138    """
139    A convenience function to call series_growth_plot() and finalise_plot().
140    Use this when you are providing the series data, for mgplot to calculate
141    the growth series.
142    """
143
144    plot_then_finalise(
145        data=data,
146        function=series_growth_plot,
147        **kwargs,
148    )

A convenience function to call series_growth_plot() and finalise_plot(). Use this when you are providing the series data, for mgplot to calculate the growth series.

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

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 = {'color': (<class 'str'>, <class 'collections.abc.Sequence'>, (<class 'str'>,)), 'width': <class 'float'>, 'stacked': <class 'bool'>, 'rotation': (<class 'int'>, <class 'float'>), 'bar_legend': <class 'bool'>, 'max_ticks': <class 'int'>, 'plot_from': (<class 'int'>, <class 'pandas.core.indexes.period.PeriodIndex'>, <class 'NoneType'>)}
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 'str'>, <class 'int'>, <class 'NoneType'>), 'plot_from': (<class 'int'>, <class 'pandas._libs.tslibs.period.Period'>, <class 'NoneType'>), 'legend': (<class 'dict'>, (<class 'str'>, <class 'object'>), <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 'str'>, <class 'int'>, <class 'NoneType'>), 'plot_from': (<class 'int'>, <class 'pandas._libs.tslibs.period.Period'>, <class 'NoneType'>), 'legend': (<class 'dict'>, (<class 'str'>, <class 'object'>), <class 'bool'>, <class 'NoneType'>)}
POSTCOVID_KW_TYPES = {'start_r': <class 'pandas._libs.tslibs.period.Period'>, 'end_r': <class 'pandas._libs.tslibs.period.Period'>, '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 'str'>, <class 'int'>, <class 'NoneType'>), 'plot_from': (<class 'int'>, <class 'pandas._libs.tslibs.period.Period'>, <class 'NoneType'>), 'legend': (<class 'dict'>, (<class 'str'>, <class 'object'>), <class 'bool'>, <class 'NoneType'>)}
REVISION_KW_TYPES = {'rounding': (<class 'collections.abc.Sequence'>, (<class 'bool'>, <class 'int'>), <class 'int'>, <class 'bool'>, <class 'NoneType'>), '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'>,)), 'fontsize': (<class 'collections.abc.Sequence'>, (<class 'str'>, <class 'int'>), <class 'str'>, <class 'int'>, <class 'NoneType'>), 'plot_from': (<class 'int'>, <class 'pandas._libs.tslibs.period.Period'>, <class 'NoneType'>), 'legend': (<class 'dict'>, (<class 'str'>, <class 'object'>), <class 'bool'>, <class 'NoneType'>)}
RUN_KW_TYPES = {'threshold': <class 'float'>, 'round': <class 'int'>, 'highlight': (<class 'str'>, <class 'collections.abc.Sequence'>, (<class 'str'>,)), 'direction': <class 'str'>, '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 'str'>, <class 'int'>, <class 'NoneType'>), 'plot_from': (<class 'int'>, <class 'pandas._libs.tslibs.period.Period'>, <class 'NoneType'>), 'legend': (<class 'dict'>, (<class 'str'>, <class 'object'>), <class 'bool'>, <class 'NoneType'>)}
SUMMARY_KW_TYPES = {'verbose': <class 'bool'>, 'middle': <class 'float'>, 'plot_type': <class 'str'>, 'plot_from': (<class 'int'>, <class 'pandas._libs.tslibs.period.Period'>, <class 'NoneType'>), 'legend': (<class 'NoneType'>, <class 'bool'>, <class 'dict'>, (<class 'str'>, <class 'object'>))}
SERIES_GROWTH_KW_TYPES = {'ylabel': (<class 'str'>, <class 'NoneType'>), 'line_width': (<class 'float'>, <class 'int'>), 'line_color': <class 'str'>, 'line_style': <class 'str'>, 'annotate_line': (<class 'NoneType'>, <class 'bool'>, <class 'int'>, <class 'str'>), 'rounding': (<class 'NoneType'>, <class 'bool'>, <class 'int'>), 'bar_width': <class 'float'>, 'bar_color': <class 'str'>, 'annotate_bar': (<class 'NoneType'>, <class 'bool'>, <class 'int'>, <class 'str'>), 'bar_rounding': (<class 'NoneType'>, <class 'bool'>, <class 'int'>), 'plot_from': (<class 'NoneType'>, <class 'pandas._libs.tslibs.period.Period'>, <class 'int'>), 'max_ticks': <class 'int'>, 'legend': (<class 'NoneType'>, <class 'bool'>, <class 'dict'>, (<class 'str'>, <class 'object'>))}
RAW_GROWTH_KW_TYPES = {'line_width': (<class 'float'>, <class 'int'>), 'line_color': <class 'str'>, 'line_style': <class 'str'>, 'annotate_line': (<class 'NoneType'>, <class 'bool'>, <class 'int'>, <class 'str'>), 'rounding': (<class 'NoneType'>, <class 'bool'>, <class 'int'>), 'bar_width': <class 'float'>, 'bar_color': <class 'str'>, 'annotate_bar': (<class 'NoneType'>, <class 'bool'>, <class 'int'>, <class 'str'>), 'bar_rounding': (<class 'NoneType'>, <class 'bool'>, <class 'int'>), 'plot_from': (<class 'NoneType'>, <class 'pandas._libs.tslibs.period.Period'>, <class 'int'>), 'max_ticks': <class 'int'>, 'legend': (<class 'NoneType'>, <class 'bool'>, <class 'dict'>, (<class 'str'>, <class 'object'>))}