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:
211def bar_plot(
212    data: DataT,
213    **kwargs,
214) -> Axes:
215    """
216    Create a bar plot from the given data. Each column in the DataFrame
217    will be stacked on top of each other, with positive values above
218    zero and negative values below zero.
219
220    Parameters
221    - data: Series - The data to plot. Can be a DataFrame or a Series.
222    - **kwargs: dict Additional keyword arguments for customization.
223    # --- options for the entire bar plot
224    ax: Axes - axes to plot on, or None for new axes
225    stacked: bool - if True, the bars will be stacked. If False, they will be grouped.
226    max_ticks: int - maximum number of ticks on the x-axis (for PeriodIndex only)
227    plot_from: int | PeriodIndex - if provided, the plot will start from this index.
228    # --- options for each bar ...
229    color: str | list[str] - the color of the bars (or separate colors for each series
230    label_series: bool | list[bool] - if True, the series will be labeled in the legend
231    width: float | list[float] - the width of the bars
232    # - options for bar annotations
233    annotate: bool - If True them annotate the bars with their values.
234    fontsize: int | float | str - font size of the annotations
235    fontname: str - font name of the annotations
236    rounding: int - number of decimal places to round to
237    annotate_color: str  - color of annotations
238    rotation: int | float - rotation of annotations in degrees
239    above: bool - if True, annotations are above the bar, else within the bar
240
241    Note: This function does not assume all data is timeseries with a PeriodIndex,
242
243    Returns
244    - axes: Axes - The axes for the plot.
245    """
246
247    # --- check the kwargs
248    me = "bar_plot"
249    report_kwargs(called_from=me, **kwargs)
250    kwargs = validate_kwargs(BAR_KW_TYPES, me, **kwargs)
251
252    # --- get the data
253    # no call to check_clean_timeseries here, as bar plots are not
254    # necessarily timeseries data. If the data is a Series, it will be
255    # converted to a DataFrame with a single column.
256    df = DataFrame(data)  # really we are only plotting DataFrames
257    df, kwargs = constrain_data(df, **kwargs)
258    item_count = len(df.columns)
259
260    # --- deal with complete PeriodIdex indicies
261    if not is_categorical(df):
262        print(
263            "Warning: bar_plot is not designed for incomplete or non-categorical data indexes."
264        )
265    saved_pi = map_periodindex(df)
266    if saved_pi is not None:
267        df = saved_pi[0]  # extract the reindexed DataFrame from the PeriodIndex
268
269    # --- set up the default arguments
270    chart_defaults: dict[str, Any] = {
271        STACKED: False,
272        MAX_TICKS: 10,
273        LABEL_SERIES: item_count > 1,
274    }
275    chart_args = {k: kwargs.get(k, v) for k, v in chart_defaults.items()}
276
277    bar_defaults: dict[str, Any] = {
278        COLOR: get_color_list(item_count),
279        WIDTH: get_setting("bar_width"),
280        LABEL_SERIES: (item_count > 1),
281    }
282    above = kwargs.get(ABOVE, False)
283    anno_args = {
284        ANNOTATE: kwargs.get(ANNOTATE, False),
285        FONTSIZE: kwargs.get(FONTSIZE, "small"),
286        FONTNAME: kwargs.get(FONTNAME, "Helvetica"),
287        ROTATION: kwargs.get(ROTATION, 0),
288        ROUNDING: kwargs.get(ROUNDING, True),
289        COLOR: kwargs.get(ANNOTATE_COLOR, "black" if above else "white"),
290        ABOVE: above,
291    }
292    bar_args, remaining_kwargs = apply_defaults(item_count, bar_defaults, kwargs)
293
294    # --- plot the data
295    axes, _rkwargs = get_axes(**remaining_kwargs)
296    if chart_args[STACKED]:
297        stacked(axes, df, anno_args, **bar_args)
298    else:
299        grouped(axes, df, anno_args, **bar_args)
300
301    # --- handle complete periodIndex data and label rotation
302    rotate_labels = True
303    if saved_pi is not None:
304        set_labels(axes, saved_pi[1], chart_args["max_ticks"])
305        rotate_labels = False
306
307    if rotate_labels:
308        plt.xticks(rotation=90)
309
310    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] = (
151        kwargs.get(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:
207def summary_plot(
208    data: DataT,  # summary data
209    **kwargs,
210) -> Axes:
211    """Plot a summary of historical data for a given DataFrame.
212
213    Args:
214    - summary: DataFrame containing the summary data. The column names are
215      used as labels for the plot.
216    - kwargs: additional arguments for the plot, including:
217        - plot_from: int | Period | None
218        - verbose: if True, print the summary data.
219        - middle: proportion of data to highlight (default is 0.8).
220        - plot_types: list of plot types to generate.
221
222
223    Returns Axes.
224    """
225
226    # --- check the kwargs
227    me = "summary_plot"
228    report_kwargs(called_from=me, **kwargs)
229    kwargs = validate_kwargs(SUMMARY_KW_TYPES, me, **kwargs)
230
231    # --- check the data
232    data = check_clean_timeseries(data, me)
233    if not isinstance(data, DataFrame):
234        raise TypeError("data must be a pandas DataFrame for summary_plot()")
235    df = DataFrame(data)  # syntactic sugar for type hinting
236
237    # --- optional arguments
238    verbose = kwargs.pop("verbose", False)
239    middle = float(kwargs.pop("middle", 0.8))
240    plot_type = kwargs.pop("plot_type", ZSCORES)
241    kwargs["legend"] = kwargs.get(
242        "legend",
243        {
244            # put the legend below the x-axis label
245            "loc": "upper center",
246            "fontsize": "xx-small",
247            "bbox_to_anchor": (0.5, -0.125),
248            "ncol": 4,
249        },
250    )
251
252    # get the data, calculate z-scores and scaled scores based on the start period
253    subset, kwargs = constrain_data(df, **kwargs)
254    z_scores, z_scaled = _calculate_z(subset, middle, verbose=verbose)
255
256    # plot as required by the plot_types argument
257    adjusted = z_scores if plot_type == ZSCORES else z_scaled
258    ax = _horizontal_bar_plot(subset, adjusted, middle, plot_type, kwargs)
259    ax.tick_params(axis="y", labelsize="small")
260    make_legend(ax, kwargs["legend"])
261    ax.set_xlim(kwargs.get("xlim", None))  # provide space for the labels
262
263    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:
146def calc_growth(series: Series) -> DataFrame:
147    """
148    Calculate annual and periodic growth for a pandas Series,
149    where the index is a PeriodIndex.
150
151    Args:
152    -   series: A pandas Series with an appropriate PeriodIndex.
153
154    Returns a two column DataFrame:
155
156    Raises
157    -   TypeError if the series is not a pandas Series.
158    -   TypeError if the series index is not a PeriodIndex.
159    -   ValueError if the series is empty.
160    -   ValueError if the series index does not have a frequency of Q, M, or D.
161    -   ValueError if the series index has duplicates.
162    """
163
164    # --- sanity checks
165    if not isinstance(series, Series):
166        raise TypeError("The series argument must be a pandas Series")
167    if not isinstance(series.index, PeriodIndex):
168        raise TypeError("The series index must be a pandas PeriodIndex")
169    if series.empty:
170        raise ValueError("The series argument must not be empty")
171    if series.index.freqstr[0] not in ("Q", "M", "D"):
172        raise ValueError("The series index must have a frequency of Q, M, or D")
173    if series.index.has_duplicates:
174        raise ValueError("The series index must not have duplicate values")
175
176    # --- ensure the index is complete and the date is sorted
177    complete = period_range(start=series.index.min(), end=series.index.max())
178    series = series.reindex(complete, fill_value=nan)
179    series = series.sort_index(ascending=True)
180
181    # --- calculate annual and periodic growth
182    ppy = {"Q": 4, "M": 12, "D": 365}[PeriodIndex(series.index).freqstr[:1]]
183    annual = series.pct_change(periods=ppy) * 100
184    periodic = series.pct_change(periods=1) * 100
185    periodic_name = {4: "Quarterly", 12: "Monthly", 365: "Daily"}[ppy] + " Growth"
186    return DataFrame(
187        {
188            "Annual Growth": annual,
189            periodic_name: periodic,
190        }
191    )

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:
214def growth_plot(
215    data: DataT,
216    **kwargs,
217) -> Axes:
218    """
219    Plot annual growth (as a line) and periodic growth (as bars)
220    on the same axes.
221
222    Args:
223    -   data: A pandas DataFrame with two columns:
224    -   kwargs:
225        -   line_width: The width of the line (default is 2).
226        -   line_color: The color of the line (default is "darkblue").
227        -   line_style: The style of the line (default is "-").
228        -   annotate_line: None | bool | int | str - fontsize to annotate
229            the line (default is "small", which means the line is annotated with
230            small text).
231        -   rounding: None | bool | int - the number of decimal places to round
232            the line (default is 0).
233        -   bar_width: The width of the bars (default is 0.8).
234        -   bar_color: The color of the bars (default is "indianred").
235        -   annotate_bar: None | int | str - fontsize to annotate the bars
236            (default is "small", which means the bars are annotated with
237            small text).
238        -   bar_rounding: The number of decimal places to round the
239            annotations to (default is 1).
240        -   plot_from: None | Period | int -- if:
241            -   None: the entire series is plotted
242            -   Period: the plot starts from this period
243            -   int: the plot starts from this +/- index position
244        -   max_ticks: The maximum number of ticks to show on the x-axis
245            (default is 10).
246
247    Returns:
248    -   axes: The matplotlib Axes object.
249
250    Raises:
251    -   TypeError if the annual and periodic arguments are not pandas Series.
252    -   TypeError if the annual index is not a PeriodIndex.
253    -   ValueError if the annual and periodic series do not have the same index.
254    """
255
256    # --- check the kwargs
257    me = "growth_plot"
258    report_kwargs(called_from=me, **kwargs)
259    kwargs = validate_kwargs(GROWTH_KW_TYPES, me, **kwargs)
260
261    # --- data checks
262    data = check_clean_timeseries(data, me)
263    if len(data.columns) != 2:
264        raise TypeError("The data argument must be a pandas DataFrame with two columns")
265    data, kwargs = constrain_data(data, **kwargs)
266
267    # --- get the series of interest ...
268    annual = data[data.columns[0]]
269    periodic = data[data.columns[1]]
270
271    # --- series names
272    annual.name = "Annual Growth"
273    periodic.name = {"M": "Monthly", "Q": "Quarterly", "D": "Daily"}[
274        PeriodIndex(periodic.index).freqstr[:1]
275    ] + " Growth"
276
277    # --- convert PeriodIndex periodic growth data to integer indexed data.
278    saved_pi = map_periodindex(periodic)
279    if saved_pi is not None:
280        periodic = saved_pi[0]  # extract the reindexed DataFrame
281
282    # --- simple bar chart for the periodic growth
283    if BAR_ANNO_COLOR not in kwargs or kwargs[BAR_ANNO_COLOR] is None:
284        kwargs[BAR_ANNO_COLOR] = "black" if kwargs.get(ABOVE, False) else "white"
285    selected = package_kwargs(to_bar_plot, **kwargs)
286    axes = bar_plot(periodic, **selected)
287
288    # --- and now the annual growth as a line
289    selected = package_kwargs(to_line_plot, **kwargs)
290    line_plot(annual, ax=axes, **selected)
291
292    # --- fix the x-axis labels
293    if saved_pi is not None:
294        set_labels(axes, saved_pi[1], kwargs.get("max_ticks", 10))
295
296    # --- and done ...
297    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:
    • 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:
300def series_growth_plot(
301    data: DataT,
302    **kwargs,
303) -> Axes:
304    """
305    Plot annual and periodic growth in percentage terms from
306    a pandas Series, and finalise the plot.
307
308    Args:
309    -   data: A pandas Series with an appropriate PeriodIndex.
310    -   kwargs:
311        -   takes the same kwargs as for growth_plot()
312    """
313
314    # --- check the kwargs
315    me = "series_growth_plot"
316    report_kwargs(called_from=me, **kwargs)
317    kwargs = validate_kwargs(SERIES_GROWTH_KW_TYPES, me, **kwargs)
318
319    # --- sanity checks
320    if not isinstance(data, Series):
321        raise TypeError(
322            "The data argument to series_growth_plot() must be a pandas Series"
323        )
324
325    # --- calculate growth and plot - add ylabel
326    ylabel: str | None = kwargs.pop("ylabel", None)
327    if ylabel is not None:
328        print(f"Did you intend to specify a value for the 'ylabel' in {me}()?")
329    ylabel = "Growth (%)" if ylabel is None else ylabel
330    growth = calc_growth(data)
331    ax = growth_plot(growth, **kwargs)
332    ax.set_ylabel(ylabel)
333    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:
72def line_plot_finalise(
73    data: DataT,
74    **kwargs,
75) -> None:
76    """
77    A convenience function to call line_plot() then finalise_plot().
78    """
79    impose_legend(data=data, kwargs=kwargs)
80    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:
84def bar_plot_finalise(
85    data: DataT,
86    **kwargs,
87) -> None:
88    """
89    A convenience function to call bar_plot() and finalise_plot().
90    """
91    impose_legend(data=data, kwargs=kwargs)
92    plot_then_finalise(
93        data,
94        function=bar_plot,
95        **kwargs,
96    )

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

def seastrend_plot_finalise(data: ~DataT, **kwargs) -> None:
 99def seastrend_plot_finalise(
100    data: DataT,
101    **kwargs,
102) -> None:
103    """
104    A convenience function to call seas_trend_plot() and finalise_plot().
105    """
106    impose_legend(force=True, kwargs=kwargs)
107    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:
110def postcovid_plot_finalise(
111    data: DataT,
112    **kwargs,
113) -> None:
114    """
115    A convenience function to call postcovid_plot() and finalise_plot().
116    """
117    impose_legend(force=True, kwargs=kwargs)
118    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:
121def revision_plot_finalise(
122    data: DataT,
123    **kwargs,
124) -> None:
125    """
126    A convenience function to call revision_plot() and finalise_plot().
127    """
128    impose_legend(force=True, kwargs=kwargs)
129    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:
160def summary_plot_finalise(
161    data: DataT,
162    **kwargs,
163) -> None:
164    """
165    A convenience function to call summary_plot() and finalise_plot().
166    This is more complex than most convienience methods.
167
168    Arguments
169    - data: DataFrame containing the summary data. The index must be a PeriodIndex.
170    - kwargs: additional arguments for the plot, including:
171        - plot_from: int | Period | None  (None means plot from 1995-01-01)
172        - verbose: if True, print the summary data.
173        - middle: proportion of data to highlight (default is 0.8).
174        - plot_type: list of plot types to generate (either "zscores" or "zscaled")
175          defaults to "zscores".
176    """
177
178    # --- standard arguments
179    kwargs[TITLE] = kwargs.get(TITLE, f"Summary at {data.index[-1]}")
180    kwargs[PRESERVE_LIMS] = kwargs.get(PRESERVE_LIMS, True)
181
182    start: None | int | Period = kwargs.get(PLOT_FROM, None)
183    if start is None:
184        start = data.index[0]
185    if isinstance(start, int):
186        start = data.index[start]
187    kwargs[PLOT_FROM] = start
188
189    for plot_type in (ZSCORES, ZSCALED):
190        # some sorting of kwargs for plot production
191        kwargs[PLOT_TYPE] = plot_type
192        kwargs[PRE_TAG] = plot_type  # necessary because the title is the same
193
194        if plot_type == "zscores":
195            kwargs[XLABEL] = f"Z-scores for prints since {start}"
196            kwargs[X0] = True
197        else:
198            kwargs[XLABEL] = f"-1 to 1 scaled z-scores since {start}"
199            kwargs.pop(X0, None)
200
201        plot_then_finalise(
202            data,
203            function=summary_plot,
204            **kwargs,
205        )

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:
150def growth_plot_finalise(data: DataT, **kwargs) -> None:
151    """
152    A convenience function to call series_growth_plot() and finalise_plot().
153    Use this when you are providing the raw growth data. Don't forget to
154    set the ylabel in kwargs.
155    """
156    impose_legend(force=True, kwargs=kwargs)
157    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:
142def series_growth_plot_finalise(data: DataT, **kwargs) -> None:
143    """
144    A convenience function to call series_growth_plot() and finalise_plot().
145    """
146    impose_legend(force=True, kwargs=kwargs)
147    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:
132def run_plot_finalise(
133    data: DataT,
134    **kwargs,
135) -> None:
136    """
137    A convenience function to call run_plot() and finalise_plot().
138    """
139    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.core.indexes.period.PeriodIndex'>, <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 'str'>, <class 'int'>, <class 'NoneType'>), '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 'str'>, <class 'int'>, <class 'NoneType'>), '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 'str'>, <class 'int'>, <class 'NoneType'>), '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 'str'>, <class 'int'>, <class 'NoneType'>), '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 'str'>, <class 'int'>, <class 'NoneType'>), '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'>), 'line_width': (<class 'float'>, <class 'int'>), 'line_color': <class 'str'>, 'line_style': <class 'str'>, 'annotate_line': (<class 'NoneType'>, <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'>, <class 'bool'>, <class 'NoneType'>), 'annotate_bars': (<class 'NoneType'>, <class 'bool'>), 'bar_fontsize': (<class 'str'>, <class 'int'>, <class 'float'>), 'bar_fontname': <class 'str'>, 'bar_rounding': (<class 'bool'>, <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'>, 'ax': (<class 'matplotlib.axes._axes.Axes'>, <class 'NoneType'>), 'plot_from': (<class 'NoneType'>, <class 'pandas._libs.tslibs.period.Period'>, <class 'int'>), 'label_series': <class 'bool'>, 'max_ticks': <class 'int'>}
GROWTH_KW_TYPES = {'line_width': (<class 'float'>, <class 'int'>), 'line_color': <class 'str'>, 'line_style': <class 'str'>, 'annotate_line': (<class 'NoneType'>, <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'>, <class 'bool'>, <class 'NoneType'>), 'annotate_bars': (<class 'NoneType'>, <class 'bool'>), 'bar_fontsize': (<class 'str'>, <class 'int'>, <class 'float'>), 'bar_fontname': <class 'str'>, 'bar_rounding': (<class 'bool'>, <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'>, 'ax': (<class 'matplotlib.axes._axes.Axes'>, <class 'NoneType'>), 'plot_from': (<class 'NoneType'>, <class 'pandas._libs.tslibs.period.Period'>, <class 'int'>), 'label_series': <class 'bool'>, 'max_ticks': <class 'int'>}