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, axis_utils nor keyword_checking modules here. 14from mgplot.bar_plot import bar_plot, BarKwargs 15from mgplot.line_plot import line_plot, LineKwargs 16from mgplot.seastrend_plot import seastrend_plot 17from mgplot.postcovid_plot import postcovid_plot, PostcovidKwargs 18from mgplot.run_plot import run_plot, RunKwargs 19from mgplot.revision_plot import revision_plot 20from mgplot.growth_plot import ( 21 growth_plot, 22 GrowthKwargs, 23 series_growth_plot, 24 SeriesGrowthKwargs, 25 calc_growth, 26) 27from mgplot.summary_plot import summary_plot, SummaryKwargs 28from mgplot.multi_plot import plot_then_finalise, multi_start, multi_column 29from mgplot.finalisers import ( 30 bar_plot_finalise, 31 line_plot_finalise, 32 postcovid_plot_finalise, 33 growth_plot_finalise, 34 revision_plot_finalise, 35 run_plot_finalise, 36 seastrend_plot_finalise, 37 series_growth_plot_finalise, 38 summary_plot_finalise, 39) 40from mgplot.finalise_plot import finalise_plot, FinaliseKwargs 41from mgplot.colors import ( 42 get_color, 43 get_party_palette, 44 colorise_list, 45 contrast, 46 abbreviate_state, 47 state_names, 48 state_abbrs, 49) 50from mgplot.settings import ( 51 get_setting, 52 set_setting, 53 set_chart_dir, 54 clear_chart_dir, 55) 56 57 58# --- version and author 59try: 60 __version__ = importlib.metadata.version(__name__) 61except importlib.metadata.PackageNotFoundError: 62 __version__ = "0.0.0" # Fallback for development mode 63__author__ = "Bryan Palmer" 64 65 66# --- public API 67__all__ = ( 68 "__version__", 69 "__author__", 70 # --- settings 71 "get_setting", 72 "set_setting", 73 "set_chart_dir", 74 "clear_chart_dir", 75 # --- colors 76 "get_color", 77 "get_party_palette", 78 "colorise_list", 79 "contrast", 80 "abbreviate_state", 81 "state_names", 82 "state_abbrs", 83 # --- bar plot 84 "bar_plot", 85 "BarKwargs", 86 # --- line plot 87 "line_plot", 88 "LineKwargs", 89 # --- seasonal + trend plot 90 "seastrend_plot", 91 # --- post-COVID plot 92 "postcovid_plot", 93 "PostcovidKwargs", 94 # --- run plot 95 "run_plot", 96 "RunKwargs", 97 # --- revision plot 98 "revision_plot", 99 # --- growth plot 100 "growth_plot", 101 "GrowthKwargs", 102 "series_growth_plot", 103 "SeriesGrowthKwargs", 104 "calc_growth", 105 # --- summary plot 106 "summary_plot", 107 "SummaryKwargs", 108 # --- multi plot 109 "multi_start", 110 "multi_column", 111 "plot_then_finalise", 112 # --- finalise plot 113 "finalise_plot", 114 "FinaliseKwargs", 115 # --- finalisers 116 "bar_plot_finalise", 117 "line_plot_finalise", 118 "postcovid_plot_finalise", 119 "growth_plot_finalise", 120 "revision_plot_finalise", 121 "run_plot_finalise", 122 "seastrend_plot_finalise", 123 "series_growth_plot_finalise", 124 "summary_plot_finalise", 125 # --- The rest are internal use only 126) 127# __pdoc__: dict[str, Any] = {"test": False} # hide submodules from documentation
88def get_setting(setting: str) -> Any: 89 """ 90 Get a setting from the global settings. 91 92 Arguments: 93 - setting: str - name of the setting to get. The possible settings are: 94 - file_type: str - the file type to use for saving plots 95 - figsize: tuple[float, float] - the figure size to use for plots 96 - file_dpi: int - the DPI to use for saving plots 97 - line_narrow: float - the line width for narrow lines 98 - line_normal: float - the line width for normal lines 99 - line_wide: float - the line width for wide lines 100 - bar_width: float - the width of bars in bar plots 101 - legend_font_size: float | str - the font size for legends 102 - legend: dict[str, Any] - the legend settings 103 - colors: dict[int, list[str]] - a dictionary of colors for 104 different numbers of lines 105 - chart_dir: str - the directory to save charts in 106 107 Raises: 108 - KeyError: if the setting is not found 109 110 Returns: 111 - value: Any - the value of the setting 112 """ 113 if setting not in _mgplot_defaults: 114 raise KeyError(f"Setting '{setting}' not found in _mgplot_defaults.") 115 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
118def set_setting(setting: str, value: Any) -> None: 119 """ 120 Set a setting in the global settings. 121 Raises KeyError if the setting is not found. 122 123 Arguments: 124 - setting: str - name of the setting to set (see get_setting()) 125 - value: Any - the value to set the setting to 126 """ 127 128 if setting not in _mgplot_defaults: 129 raise KeyError(f"Setting '{setting}' not found in _mgplot_defaults.") 130 _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
149def set_chart_dir(chart_dir: str) -> None: 150 """ 151 A function to set a global chart directory for finalise_plot(), 152 so that it does not need to be included as an argument in each 153 call to finalise_plot(). Create the directory if it does not exist. 154 155 Note: Path.mkdir() may raise an exception if a directory cannot be created. 156 157 Note: This is a wrapper for set_setting() to set the chart_dir setting, and 158 create the directory if it does not exist. 159 160 Arguments: 161 - chart_dir: str - the directory to set as the chart directory 162 """ 163 164 if not chart_dir: 165 chart_dir = "." # avoid the empty string 166 Path(chart_dir).mkdir(parents=True, exist_ok=True) 167 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
133def clear_chart_dir() -> None: 134 """ 135 Remove all graph-image files from the global chart_dir. 136 This is a convenience function to remove all files from the 137 chart_dir directory. It does not remove the directory itself. 138 Note: the function creates the directory if it does not exist. 139 """ 140 141 chart_dir = get_setting("chart_dir") 142 Path(chart_dir).mkdir(parents=True, exist_ok=True) 143 for ext in ("png", "svg", "jpg", "jpeg"): 144 for fs_object in Path(chart_dir).glob(f"*.{ext}"): 145 if fs_object.is_file(): 146 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.
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.
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.
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.
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.
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.
188def bar_plot(data: DataT, **kwargs: Unpack[BarKwargs]) -> Axes: 189 """ 190 Create a bar plot from the given data. Each column in the DataFrame 191 will be stacked on top of each other, with positive values above 192 zero and negative values below zero. 193 194 Parameters 195 - data: Series - The data to plot. Can be a DataFrame or a Series. 196 - **kwargs: BarKwargs - Additional keyword arguments for customization. 197 (see BarKwargs for details) 198 199 Note: This function does not assume all data is timeseries with a PeriodIndex, 200 201 Returns 202 - axes: Axes - The axes for the plot. 203 """ 204 205 # --- check the kwargs 206 report_kwargs(caller=ME, **kwargs) 207 validate_kwargs(schema=BarKwargs, caller=ME, **kwargs) 208 209 # --- get the data 210 # no call to check_clean_timeseries here, as bar plots are not 211 # necessarily timeseries data. If the data is a Series, it will be 212 # converted to a DataFrame with a single column. 213 df = DataFrame(data) # really we are only plotting DataFrames 214 df, kwargs_d = constrain_data(df, **kwargs) 215 item_count = len(df.columns) 216 217 # --- deal with complete PeriodIdex indicies 218 if not is_categorical(df): 219 print("Warning: bar_plot is not designed for incomplete or non-categorical data indexes.") 220 saved_pi = map_periodindex(df) 221 if saved_pi is not None: 222 df = saved_pi[0] # extract the reindexed DataFrame from the PeriodIndex 223 224 # --- set up the default arguments 225 chart_defaults: dict[str, Any] = { 226 "stacked": False, 227 "max_ticks": 10, 228 "label_series": item_count > 1, 229 } 230 chart_args = {k: kwargs_d.get(k, v) for k, v in chart_defaults.items()} 231 232 bar_defaults: dict[str, Any] = { 233 "color": get_color_list(item_count), 234 "width": get_setting("bar_width"), 235 "label_series": (item_count > 1), 236 } 237 above = kwargs_d.get("above", False) 238 anno_args = { 239 "annotate": kwargs_d.get("annotate", False), 240 "fontsize": kwargs_d.get("fontsize", "small"), 241 "fontname": kwargs_d.get("fontname", "Helvetica"), 242 "rotation": kwargs_d.get("rotation", 0), 243 "rounding": kwargs_d.get("rounding", True), 244 "color": kwargs_d.get("annotate_color", "black" if above else "white"), 245 "above": above, 246 } 247 bar_args, remaining_kwargs = apply_defaults(item_count, bar_defaults, kwargs_d) 248 249 # --- plot the data 250 axes, remaining_kwargs = get_axes(**remaining_kwargs) 251 if chart_args["stacked"]: 252 stacked(axes, df, anno_args, **bar_args) 253 else: 254 grouped(axes, df, anno_args, **bar_args) 255 256 # --- handle complete periodIndex data and label rotation 257 if saved_pi is not None: 258 set_labels(axes, saved_pi[1], chart_args["max_ticks"]) 259 else: 260 plt.xticks(rotation=90) 261 262 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: BarKwargs - Additional keyword arguments for customization. (see BarKwargs for details)
Note: This function does not assume all data is timeseries with a PeriodIndex,
Returns
- axes: Axes - The axes for the plot.
38class BarKwargs(BaseKwargs): 39 """Keyword arguments for the bar_plot function.""" 40 41 # --- options for the entire bar plot 42 ax: NotRequired[Axes | None] 43 stacked: NotRequired[bool] 44 max_ticks: NotRequired[int] 45 plot_from: NotRequired[int | Period] 46 # --- options for each bar ... 47 color: NotRequired[str | Sequence[str]] 48 label_series: NotRequired[bool | Sequence[bool]] 49 width: NotRequired[float | int | Sequence[float | int]] 50 # --- options for bar annotations 51 annotate: NotRequired[bool] 52 fontsize: NotRequired[int | float | str] 53 fontname: NotRequired[str] 54 rounding: NotRequired[int] 55 rotation: NotRequired[int | float] 56 annotate_color: NotRequired[str] 57 above: NotRequired[bool]
Keyword arguments for the bar_plot function.
128def line_plot(data: DataT, **kwargs: Unpack[LineKwargs]) -> Axes: 129 """ 130 Build a single plot from the data passed in. 131 This can be a single- or multiple-line plot. 132 Return the axes object for the build. 133 134 Arguments: 135 - data: DataFrame | Series - data to plot 136 - kwargs: Unpack[LineKwargs] 137 138 Returns: 139 - axes: Axes - the axes object for the plot 140 """ 141 142 # --- check the kwargs 143 report_kwargs(caller=ME, **kwargs) 144 validate_kwargs(schema=LineKwargs, caller=ME, **kwargs) 145 146 # --- check the data 147 data = check_clean_timeseries(data, ME) 148 df = DataFrame(data) # we are only plotting DataFrames 149 df, kwargs_d = constrain_data(df, **kwargs) 150 151 # --- convert PeriodIndex to Integer Index 152 saved_pi = map_periodindex(df) 153 if saved_pi is not None: 154 df = saved_pi[0] 155 156 # --- some special defaults 157 kwargs_d["label_series"] = ( 158 kwargs_d.get("label_series", True) if len(df.columns) > 1 else kwargs_d.get("label_series", False) 159 ) 160 161 # --- Let's plot 162 axes, kwargs_d = get_axes(**kwargs_d) # get the axes to plot on 163 if df.empty or df.isna().all().all(): 164 # Note: finalise plot should ignore an empty axes object 165 print(f"Warning: No data to plot in {ME}().") 166 return axes 167 168 # --- get the arguments for each line we will plot ... 169 item_count = len(df.columns) 170 num_data_points = len(df) 171 swce, kwargs_d = _get_style_width_color_etc(item_count, num_data_points, **kwargs_d) 172 173 for i, column in enumerate(df.columns): 174 series = df[column] 175 series = series.dropna() if "dropna" in swce and swce["dropna"][i] else series 176 if series.empty or series.isna().all(): 177 print(f"Warning: No data to plot for {column} in line_plot().") 178 continue 179 180 series.plot( 181 # Note: pandas will plot PeriodIndex against their ordinal values 182 ls=swce["style"][i], 183 lw=swce["width"][i], 184 color=swce["color"][i], 185 alpha=swce["alpha"][i], 186 marker=swce["marker"][i], 187 ms=swce["markersize"][i], 188 drawstyle=swce["drawstyle"][i], 189 label=(column if "label_series" in swce and swce["label_series"][i] else f"_{column}_"), 190 ax=axes, 191 ) 192 193 if swce["annotate"][i] is None or not swce["annotate"][i]: 194 continue 195 196 color = swce["color"][i] if swce["annotate_color"][i] is True else swce["annotate_color"][i] 197 annotate_series( 198 series, 199 axes, 200 color=color, 201 rounding=swce["rounding"][i], 202 fontsize=swce["fontsize"][i], 203 fontname=swce["fontname"][i], 204 rotation=swce["rotation"][i], 205 ) 206 207 # --- set the labels 208 if saved_pi is not None: 209 set_labels(axes, saved_pi[1], kwargs_d.get("max_ticks", get_setting("max_ticks"))) 210 211 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.
Arguments:
- data: DataFrame | Series - data to plot
- kwargs: Unpack[LineKwargs]
Returns:
- axes: Axes - the axes object for the plot
31class LineKwargs(BaseKwargs): 32 """Keyword arguments for the line_plot function.""" 33 34 # --- options for the entire line plot 35 ax: NotRequired[Axes | None] 36 style: NotRequired[str | Sequence[str]] 37 width: NotRequired[float | int | Sequence[float | int]] 38 color: NotRequired[str | Sequence[str]] 39 alpha: NotRequired[float | Sequence[float]] 40 drawstyle: NotRequired[str | Sequence[str] | None] 41 marker: NotRequired[str | Sequence[str] | None] 42 markersize: NotRequired[float | Sequence[float] | int | None] 43 dropna: NotRequired[bool | Sequence[bool]] 44 annotate: NotRequired[bool | Sequence[bool]] 45 rounding: NotRequired[Sequence[int | bool] | int | bool | None] 46 fontsize: NotRequired[Sequence[str | int | float] | str | int | float] 47 fontname: NotRequired[str | Sequence[str]] 48 rotation: NotRequired[Sequence[int | float] | int | float] 49 annotate_color: NotRequired[str | Sequence[str] | bool | Sequence[bool] | None] 50 plot_from: NotRequired[int | Period | None] 51 label_series: NotRequired[bool | Sequence[bool] | None] 52 max_ticks: NotRequired[int]
Keyword arguments for the line_plot function.
22def seastrend_plot(data: DataT, **kwargs: Unpack[LineKwargs]) -> Axes: 23 """ 24 Publish a DataFrame, where the first column is seasonally 25 adjusted data, and the second column is trend data. 26 27 Aguments: 28 - data: DataFrame - the data to plot with the first column 29 being the seasonally adjusted data, and the second column 30 being the trend data. 31 The remaining arguments are the same as those passed to 32 line_plot(). 33 34 Returns: 35 - a matplotlib Axes object 36 """ 37 38 # Note: we will rely on the line_plot() function to do most of the work. 39 # including constraining the data to the plot_from keyword argument. 40 41 # --- check the kwargs 42 report_kwargs(caller=ME, **kwargs) 43 validate_kwargs(schema=LineKwargs, caller=ME, **kwargs) 44 45 # --- check the data 46 data = check_clean_timeseries(data, ME) 47 if len(data.columns) < 2: 48 raise ValueError("seas_trend_plot() expects a DataFrame data item with at least 2 columns.") 49 50 # --- defaults if not in kwargs 51 kwargs["color"] = kwargs.get("color", get_color_list(2)) 52 kwargs["width"] = kwargs.get("width", [get_setting("line_normal"), get_setting("line_wide")]) 53 kwargs["style"] = kwargs.get("style", ["-", "-"]) 54 kwargs["annotate"] = kwargs.get("annotate", [True, False]) 55 kwargs["rounding"] = kwargs.get("rounding", True) 56 57 # series breaks are common in seas-trend data 58 kwargs["dropna"] = kwargs.get("dropna", False) 59 60 axes = line_plot( 61 data, 62 **kwargs, 63 ) 64 65 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
51def postcovid_plot(data: DataT, **kwargs: Unpack[PostcovidKwargs]) -> Axes: 52 """ 53 Plots a series with a PeriodIndex. 54 55 Arguments 56 - data - the series to be plotted (note that this function 57 is designed to work with a single series, not a DataFrame). 58 - **kwargs - same as for line_plot() and finalise_plot(). 59 60 Raises: 61 - TypeError if series is not a pandas Series 62 - TypeError if series does not have a PeriodIndex 63 - ValueError if series does not have a D, M or Q frequency 64 - ValueError if regression start is after regression end 65 """ 66 67 # --- check the kwargs 68 report_kwargs(caller=ME, **kwargs) 69 validate_kwargs(schema=PostcovidKwargs, caller=ME, **kwargs) 70 71 # --- check the data 72 data = check_clean_timeseries(data, ME) 73 if not isinstance(data, Series): 74 raise TypeError("The series argument must be a pandas Series") 75 series: Series = data 76 series_index = PeriodIndex(series.index) # syntactic sugar for type hinting 77 if series_index.freqstr[:1] not in ("Q", "M", "D"): 78 raise ValueError("The series index must have a D, M or Q freq") 79 80 # rely on line_plot() to validate kwargs 81 if "plot_from" in kwargs: 82 print("Warning: the 'plot_from' argument is ignored in postcovid_plot().") 83 del kwargs["plot_from"] 84 85 # --- plot COVID counterfactural 86 freq = PeriodIndex(series.index).freqstr # syntactic sugar for type hinting 87 match freq[0]: 88 case "Q": 89 start_regression = Period("2014Q4", freq=freq) 90 end_regression = Period("2019Q4", freq=freq) 91 case "M": 92 start_regression = Period("2015-01", freq=freq) 93 end_regression = Period("2020-01", freq=freq) 94 case "D": 95 start_regression = Period("2015-01-01", freq=freq) 96 end_regression = Period("2020-01-01", freq=freq) 97 98 start_regression = Period(kwargs.pop("start_r", start_regression), freq=freq) 99 end_regression = Period(kwargs.pop("end_r", end_regression), freq=freq) 100 if start_regression >= end_regression: 101 raise ValueError("Start period must be before end period") 102 103 # --- combine data and projection 104 recent = series[series.index >= start_regression].copy() 105 recent.name = "Series" 106 projection = get_projection(recent, end_regression) 107 projection.name = "Pre-COVID projection" 108 data_set = DataFrame([projection, recent]).T 109 110 # --- activate plot settings 111 kwargs["width"] = kwargs.pop( 112 "width", (get_setting("line_normal"), get_setting("line_wide")) 113 ) # series line is thicker than projection 114 kwargs["style"] = kwargs.pop("style", ("--", "-")) # dashed regression line 115 kwargs["label_series"] = kwargs.pop("label_series", True) 116 kwargs["annotate"] = kwargs.pop("annotate", (False, True)) # annotate series only 117 kwargs["color"] = kwargs.pop("color", ("darkblue", "#dd0000")) 118 119 return line_plot( 120 data_set, 121 **cast(LineKwargs, kwargs), 122 )
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
26class PostcovidKwargs(LineKwargs): 27 "Keyword arguments for the post-COVID plot." 28 29 start_r: NotRequired[Period] # start of regression period 30 end_r: NotRequired[Period] # end of regression period
Keyword arguments for the post-COVID plot.
101def run_plot(data: DataT, **kwargs: Unpack[RunKwargs]) -> Axes: 102 """Plot a series of percentage rates, highlighting the increasing runs. 103 104 Arguments 105 - data - ordered pandas Series of percentages, with PeriodIndex 106 - **kwargs: RunKwargs 107 108 Return 109 - matplotlib Axes object""" 110 111 # --- check the kwargs 112 report_kwargs(caller="run_plot", **kwargs) 113 validate_kwargs(schema=RunKwargs, caller=ME, **kwargs) 114 115 # --- check the data 116 series = check_clean_timeseries(data, ME) 117 if not isinstance(series, Series): 118 raise TypeError("series must be a pandas Series for run_plot()") 119 series, kwargs_d = constrain_data(series, **kwargs) 120 121 # --- convert PeriodIndex if needed 122 saved_pi = map_periodindex(series) 123 if saved_pi is not None: 124 series = saved_pi[0] 125 126 # --- default arguments - in **kwargs_d 127 kwargs_d["threshold"] = kwargs_d.get("threshold", 0.1) 128 kwargs_d["direction"] = kwargs_d.get("direction", "both") 129 kwargs_d["rounding"] = kwargs_d.get("rounding", 2) 130 kwargs_d["highlight"] = kwargs_d.get( 131 "highlight", 132 ( 133 ("gold", "skyblue") 134 if kwargs_d["direction"] == "both" 135 else "gold" 136 if kwargs_d["direction"] == "up" 137 else "skyblue" 138 ), 139 ) 140 kwargs_d["color"] = kwargs_d.get("color", "darkblue") 141 142 # --- plot the line 143 kwargs_d["drawstyle"] = kwargs_d.get("drawstyle", "steps-post") 144 lp_kwargs = limit_kwargs(LineKwargs, **kwargs_d) 145 axes = line_plot(series, **lp_kwargs) 146 147 # plot the runs 148 match kwargs["direction"]: 149 case "up": 150 _plot_runs(axes, series, up=True, **kwargs_d) 151 case "down": 152 _plot_runs(axes, series, up=False, **kwargs_d) 153 case "both": 154 _plot_runs(axes, series, up=True, **kwargs_d) 155 _plot_runs(axes, series, up=False, **kwargs_d) 156 case _: 157 raise ValueError( 158 f"Invalid value for direction: {kwargs['direction']}. Expected 'up', 'down', or 'both'." 159 ) 160 161 # --- set the labels 162 if saved_pi is not None: 163 set_labels(axes, saved_pi[1], kwargs.get("max_ticks", get_setting("max_ticks"))) 164 165 return axes
Plot a series of percentage rates, highlighting the increasing runs.
Arguments
- data - ordered pandas Series of percentages, with PeriodIndex
- **kwargs: RunKwargs
Return
- matplotlib Axes object
29class RunKwargs(LineKwargs): 30 """Keyword arguments for the run_plot function.""" 31 32 threshold: NotRequired[float] 33 highlight: NotRequired[str | Sequence[str]] 34 direction: NotRequired[str]
Keyword arguments for the run_plot function.
26def revision_plot(data: DataT, **kwargs: Unpack[LineKwargs]) -> Axes: 27 """ 28 Plot the revisions to ABS data. 29 30 Arguments 31 data: pd.DataFrame - the data to plot, the DataFrame has a 32 column for each data revision 33 kwargs - additional keyword arguments for the line_plot function. 34 """ 35 36 # --- check the kwargs and data 37 report_kwargs(caller=ME, **kwargs) 38 validate_kwargs(schema=LineKwargs, caller=ME, **kwargs) 39 data = check_clean_timeseries(data, ME) 40 41 # --- additional checks 42 if not isinstance(data, DataFrame): 43 print(f"{ME}() requires a DataFrame with columns for each revision, not a Series or any other type.") 44 45 # --- critical defaults 46 kwargs["plot_from"] = kwargs.get("plot_from", -15) 47 kwargs["annotate"] = kwargs.get("annotate", True) 48 kwargs["annotate_color"] = kwargs.get("annotate_color", "black") 49 kwargs["rounding"] = kwargs.get("rounding", 3) 50 51 # --- plot 52 axes = line_plot(data, **kwargs) 53 54 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.
155def growth_plot( 156 data: DataT, 157 **kwargs: Unpack[GrowthKwargs], 158) -> Axes: 159 """ 160 Plot annual growth (as a line) and periodic growth (as bars) 161 on the same axes. 162 163 Args: 164 - data: A pandas DataFrame with two columns: 165 - kwargs: GrowthKwargs 166 167 Returns: 168 - axes: The matplotlib Axes object. 169 170 Raises: 171 - TypeError if the annual and periodic arguments are not pandas Series. 172 - TypeError if the annual index is not a PeriodIndex. 173 - ValueError if the annual and periodic series do not have the same index. 174 """ 175 176 # --- check the kwargs 177 me = "growth_plot" 178 report_kwargs(caller=me, **kwargs) 179 validate_kwargs(GrowthKwargs, caller=me, **kwargs) 180 181 # --- data checks 182 data = check_clean_timeseries(data, me) 183 if len(data.columns) != 2: 184 raise TypeError("The data argument must be a pandas DataFrame with two columns") 185 data, kwargsd = constrain_data(data, **kwargs) 186 187 # --- get the series of interest ... 188 annual = data[data.columns[0]] 189 periodic = data[data.columns[1]] 190 191 # --- series names 192 annual.name = "Annual Growth" 193 periodic.name = {"M": "Monthly", "Q": "Quarterly", "D": "Daily"}[ 194 PeriodIndex(periodic.index).freqstr[:1] 195 ] + " Growth" 196 197 # --- convert PeriodIndex periodic growth data to integer indexed data. 198 saved_pi = map_periodindex(periodic) 199 if saved_pi is not None: 200 periodic = saved_pi[0] # extract the reindexed DataFrame 201 202 # --- simple bar chart for the periodic growth 203 if "bar_anno_color" not in kwargsd or kwargsd["bar_anno_color"] is None: 204 kwargsd["bar_anno_color"] = "black" if kwargsd.get("above", False) else "white" 205 selected = package_kwargs(to_bar_plot, **kwargsd) 206 axes = bar_plot(periodic, **selected) 207 208 # --- and now the annual growth as a line 209 selected = package_kwargs(to_line_plot, **kwargsd) 210 line_plot(annual, ax=axes, **selected) 211 212 # --- fix the x-axis labels 213 if saved_pi is not None: 214 set_labels(axes, saved_pi[1], kwargsd.get("max_ticks", 10)) 215 216 # --- and done ... 217 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: GrowthKwargs
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.
34class GrowthKwargs(BaseKwargs): 35 """Keyword arguments for the growth_plot function.""" 36 37 # --- common options 38 ax: NotRequired[Axes | None] 39 plot_from: NotRequired[int | Period] 40 label_series: NotRequired[bool] 41 max_ticks: NotRequired[int] 42 # --- options passed to the line plot 43 line_width: NotRequired[float | int] 44 line_color: NotRequired[str] 45 line_style: NotRequired[str] 46 annotate_line: NotRequired[bool] 47 line_rounding: NotRequired[bool | int] 48 line_fontsize: NotRequired[str | int | float] 49 line_fontname: NotRequired[str] 50 line_anno_color: NotRequired[str] 51 # --- options passed to the bar plot 52 annotate_bars: NotRequired[bool] 53 bar_fontsize: NotRequired[str | int | float] 54 bar_fontname: NotRequired[str] 55 bar_rounding: NotRequired[int] 56 bar_width: NotRequired[float] 57 bar_color: NotRequired[str] 58 bar_anno_color: NotRequired[str] 59 bar_rotation: NotRequired[int | float]
Keyword arguments for the growth_plot function.
220def series_growth_plot( 221 data: DataT, 222 **kwargs: Unpack[SeriesGrowthKwargs], 223) -> Axes: 224 """ 225 Plot annual and periodic growth in percentage terms from 226 a pandas Series, and finalise the plot. 227 228 Args: 229 - data: A pandas Series with an appropriate PeriodIndex. 230 - kwargs: SeriesGrowthKwargs 231 - takes much the same kwargs as for growth_plot() 232 """ 233 234 # --- check the kwargs 235 me = "series_growth_plot" 236 report_kwargs(caller=me, **kwargs) 237 validate_kwargs(SeriesGrowthKwargs, caller=me, **kwargs) 238 239 # --- sanity checks 240 if not isinstance(data, Series): 241 raise TypeError("The data argument to series_growth_plot() must be a pandas Series") 242 243 # --- calculate growth and plot - add ylabel 244 ylabel: str | None = kwargs.pop("ylabel", None) 245 if ylabel is not None: 246 print(f"Did you intend to specify a value for the 'ylabel' in {me}()?") 247 ylabel = "Growth (%)" if ylabel is None else ylabel 248 growth = calc_growth(data) 249 ax = growth_plot(growth, **cast(GrowthKwargs, kwargs)) 250 ax.set_ylabel(ylabel) 251 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: SeriesGrowthKwargs
- takes much the same kwargs as for growth_plot()
62class SeriesGrowthKwargs(GrowthKwargs): 63 """Keyword arguments for the series_growth_plot function.""" 64 65 ylabel: NotRequired[str | None]
Keyword arguments for the series_growth_plot function.
107def calc_growth(series: Series) -> DataFrame: 108 """ 109 Calculate annual and periodic growth for a pandas Series, 110 where the index is a PeriodIndex. 111 112 Args: 113 - series: A pandas Series with an appropriate PeriodIndex. 114 115 Returns a two column DataFrame: 116 117 Raises 118 - TypeError if the series is not a pandas Series. 119 - TypeError if the series index is not a PeriodIndex. 120 - ValueError if the series is empty. 121 - ValueError if the series index does not have a frequency of Q, M, or D. 122 - ValueError if the series index has duplicates. 123 """ 124 125 # --- sanity checks 126 if not isinstance(series, Series): 127 raise TypeError("The series argument must be a pandas Series") 128 if not isinstance(series.index, PeriodIndex): 129 raise TypeError("The series index must be a pandas PeriodIndex") 130 if series.empty: 131 raise ValueError("The series argument must not be empty") 132 if series.index.freqstr[0] not in ("Q", "M", "D"): 133 raise ValueError("The series index must have a frequency of Q, M, or D") 134 if series.index.has_duplicates: 135 raise ValueError("The series index must not have duplicate values") 136 137 # --- ensure the index is complete and the date is sorted 138 complete = period_range(start=series.index.min(), end=series.index.max()) 139 series = series.reindex(complete, fill_value=nan) 140 series = series.sort_index(ascending=True) 141 142 # --- calculate annual and periodic growth 143 ppy = {"Q": 4, "M": 12, "D": 365}[PeriodIndex(series.index).freqstr[:1]] 144 annual = series.pct_change(periods=ppy) * 100 145 periodic = series.pct_change(periods=1) * 100 146 periodic_name = {4: "Quarterly", 12: "Monthly", 365: "Daily"}[ppy] + " Growth" 147 return DataFrame( 148 { 149 "Annual Growth": annual, 150 periodic_name: periodic, 151 } 152 )
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.
200def summary_plot(data: DataT, **kwargs: Unpack[SummaryKwargs]) -> Axes: 201 """Plot a summary of historical data for a given DataFrame. 202 203 Args:x 204 - summary: DataFrame containing the summary data. The column names are 205 used as labels for the plot. 206 - kwargs: additional arguments for the plot, including: 207 208 Returns Axes. 209 """ 210 211 # --- check the kwargs 212 me = "summary_plot" 213 report_kwargs(caller=me, **kwargs) 214 validate_kwargs(schema=SummaryKwargs, caller=me, **kwargs) 215 216 # --- check the data 217 data = check_clean_timeseries(data, me) 218 if not isinstance(data, DataFrame): 219 raise TypeError("data must be a pandas DataFrame for summary_plot()") 220 df = DataFrame(data) # syntactic sugar for type hinting 221 222 # --- optional arguments 223 verbose = kwargs.pop("verbose", False) 224 middle = float(kwargs.pop("middle", 0.8)) 225 plot_type = kwargs.pop("plot_type", ZSCORES) 226 kwargs["legend"] = kwargs.get( 227 "legend", 228 { 229 # put the legend below the x-axis label 230 "loc": "upper center", 231 "fontsize": "xx-small", 232 "bbox_to_anchor": (0.5, -0.125), 233 "ncol": 4, 234 }, 235 ) 236 237 # get the data, calculate z-scores and scaled scores based on the start period 238 subset, kwargsd = constrain_data(df, **kwargs) 239 z_scores, z_scaled = _calculate_z(subset, middle, verbose=verbose) 240 241 # plot as required by the plot_types argument 242 adjusted = z_scores if plot_type == ZSCORES else z_scaled 243 ax = _horizontal_bar_plot(subset, adjusted, middle, plot_type, kwargsd) 244 ax.tick_params(axis="y", labelsize="small") 245 make_legend(ax, kwargsd["legend"]) 246 ax.set_xlim(kwargsd.get("xlim")) # provide space for the labels 247 248 return ax
Plot a summary of historical data for a given DataFrame.
Args:x
- summary: DataFrame containing the summary data. The column names are used as labels for the plot.
- kwargs: additional arguments for the plot, including:
Returns Axes.
36class SummaryKwargs(BaseKwargs): 37 """Keyword arguments for the summary_plot function.""" 38 39 ax: NotRequired[Axes | None] 40 verbose: NotRequired[bool] 41 middle: NotRequired[float] 42 plot_type: NotRequired[str] 43 plot_from: NotRequired[int | Period | None] 44 legend: NotRequired[dict[str, Any]]
Keyword arguments for the summary_plot function.
200def multi_start( 201 data: DataT, 202 function: Callable | list[Callable], 203 starts: Iterable[None | Period | int], 204 **kwargs, 205) -> None: 206 """ 207 Create multiple plots with different starting points. 208 Each plot will start from the specified starting point. 209 210 Parameters 211 - data: Series | DataFrame - The data to be plotted. 212 - function: Callable | list[Callable] - The plotting function 213 to be used. 214 - starts: Iterable[Period | int | None] - The starting points 215 for each plot (None means use the entire data). 216 - **kwargs: Additional keyword arguments to be passed to 217 the plotting function. 218 219 Returns None. 220 221 Raises 222 - ValueError if the starts is not an iterable of None, Period or int. 223 224 Note: kwargs['tag'] is used to create a unique tag for each plot. 225 """ 226 227 # --- sanity checks 228 me = "multi_start" 229 report_kwargs(caller=me, **kwargs) 230 if not isinstance(starts, Iterable): 231 raise ValueError("starts must be an iterable of None, Period or int") 232 # data not checked here, assume it is checked by the called 233 # plot function. 234 235 # --- check the function argument 236 original_tag: Final[str] = kwargs.get("tag", "") 237 first, kwargs["function"] = first_unchain(function) 238 if not kwargs["function"]: 239 del kwargs["function"] # remove the function key if it is empty 240 241 # --- iterate over the starts 242 for i, start in enumerate(starts): 243 kw = kwargs.copy() # copy to avoid modifying the original kwargs 244 this_tag = f"{original_tag}_{i}" 245 kw["tag"] = this_tag 246 kw["plot_from"] = start # rely on plotting function to constrain the data 247 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.
250def multi_column( 251 data: DataFrame, 252 function: Callable | list[Callable], 253 **kwargs, 254) -> None: 255 """ 256 Create multiple plots, one for each column in a DataFrame. 257 The plot title will be the column name. 258 259 Parameters 260 - data: DataFrame - The data to be plotted 261 - function: Callable - The plotting function to be used. 262 - **kwargs: Additional keyword arguments to be passed to 263 the plotting function. 264 265 Returns None. 266 """ 267 268 # --- sanity checks 269 me = "multi_column" 270 report_kwargs(caller=me, **kwargs) 271 if not isinstance(data, DataFrame): 272 raise TypeError("data must be a pandas DataFrame for multi_column()") 273 # Otherwise, the data is assumed to be checked by the called 274 # plot function, so we do not check it here. 275 276 # --- check the function argument 277 title_stem = kwargs.get("title", "") 278 tag: Final[str] = kwargs.get("tag", "") 279 first, kwargs["function"] = first_unchain(function) 280 if not kwargs["function"]: 281 del kwargs["function"] # remove the function key if it is empty 282 283 # --- iterate over the columns 284 for i, col in enumerate(data.columns): 285 series = data[[col]] 286 kwargs["title"] = f"{title_stem}{col}" if title_stem else col 287 288 this_tag = f"_{tag}_{i}".replace("__", "_") 289 kwargs["tag"] = this_tag 290 291 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.
129def plot_then_finalise( 130 data: DataT, 131 function: Callable | list[Callable], 132 **kwargs, 133) -> None: 134 """ 135 Chain a plotting function with the finalise_plot() function. 136 This is designed to be the last function in a chain. 137 138 Parameters 139 - data: Series | DataFrame - The data to be plotted. 140 - function: Callable | list[Callable] - The plotting function 141 to be used. 142 - **kwargs: Additional keyword arguments to be passed to 143 the plotting function, and then the finalise_plot() function. 144 145 Returns None. 146 """ 147 148 # --- checks 149 me = "plot_then_finalise" 150 report_kwargs(caller=me, **kwargs) 151 # validate once we have established the first function 152 153 # data is not checked here, assume it is checked by the called 154 # plot function. 155 156 first, kwargs["function"] = first_unchain(function) 157 if not kwargs["function"]: 158 del kwargs["function"] # remove the function key if it is empty 159 160 # --- TO DO: check that the first function is one of the 161 bad_next = (multi_start, multi_column) 162 if first in bad_next: 163 # these functions should not be called by plot_then_finalise() 164 raise ValueError( 165 f"[{', '.join(k.__name__ for k in bad_next)}] should not be called by {me}. " 166 "Call them before calling {me}. " 167 ) 168 169 if first in EXPECTED_CALLABLES: 170 expected = EXPECTED_CALLABLES[first] 171 plot_kwargs = limit_kwargs(expected, **kwargs) 172 else: 173 # this is an unexpected Callable, so we will give it a try 174 print(f"Unknown proposed function: {first}; nonetheless, will give it a try.") 175 expected = BaseKwargs 176 plot_kwargs = kwargs.copy() 177 178 # --- validate the original kwargs (could not do before now) 179 kw_types = ( 180 # combine the expected kwargs types with the finalise kwargs types 181 dict(cast(dict[str, Any], expected.__annotations__)) 182 | dict(cast(dict[str, Any], FinaliseKwargs.__annotations__)) 183 ) 184 validate_kwargs(schema=kw_types, caller=me, **kwargs) 185 186 # --- call the first function with the data and selected plot kwargs 187 axes = first(data, **plot_kwargs) 188 189 # --- remove potentially overlapping kwargs 190 fp_kwargs = limit_kwargs(FinaliseKwargs, **kwargs) 191 # overlapping = expected.keys() & FinaliseKwargs.keys() 192 # if overlapping: 193 # for key in overlapping: 194 # fp_kwargs.pop(key, None) # remove overlapping keys from kwargs 195 196 # --- finalise the plot 197 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.
244def finalise_plot(axes: Axes, **kwargs: Unpack[FinaliseKwargs]) -> None: 245 """ 246 A function to finalise and save plots to the file system. The filename 247 for the saved plot is constructed from the global chart_dir, the plot's title, 248 any specified tag text, and the file_type for the plot. 249 250 Arguments: 251 - axes - matplotlib axes object - required 252 - kwargs: FinaliseKwargs 253 254 Returns: 255 - None 256 """ 257 258 # --- check the kwargs 259 me = "finalise_plot" 260 report_kwargs(caller=me, **kwargs) 261 validate_kwargs(schema=FinaliseKwargs, caller=me, **kwargs) 262 263 # --- sanity checks 264 if len(axes.get_children()) < 1: 265 print("Warning: finalise_plot() called with empty axes, which was ignored.") 266 return 267 268 # --- remember axis-limits should we need to restore thems 269 xlim, ylim = axes.get_xlim(), axes.get_ylim() 270 271 # margins 272 axes.margins(0.02) 273 axes.autoscale(tight=False) # This is problematic ... 274 275 apply_kwargs(axes, **kwargs) 276 277 # tight layout and save the figure 278 fig = axes.figure 279 if "preserve_lims" in kwargs and kwargs["preserve_lims"]: 280 # restore the original limits of the axes 281 axes.set_xlim(xlim) 282 axes.set_ylim(ylim) 283 if not isinstance(fig, mpl.figure.SubFigure): # mypy 284 fig.tight_layout(pad=1.1) 285 apply_late_kwargs(axes, **kwargs) 286 legend = axes.get_legend() 287 if legend and kwargs.get("remove_legend", False): 288 legend.remove() 289 if not isinstance(fig, mpl.figure.SubFigure): # mypy 290 save_to_file(fig, **kwargs) 291 292 # show the plot in Jupyter Lab 293 if "show" in kwargs and kwargs["show"]: 294 plt.show() 295 296 # And close 297 closing = True if "dont_close" not in kwargs else not kwargs["dont_close"] 298 if closing: 299 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: FinaliseKwargs
Returns: - None
24class FinaliseKwargs(BaseKwargs): 25 """Keyword arguments for the finalise_plot function.""" 26 27 # --- value options 28 title: NotRequired[str | None] 29 xlabel: NotRequired[str | None] 30 ylabel: NotRequired[str | None] 31 xlim: NotRequired[tuple[float, float] | None] 32 ylim: NotRequired[tuple[float, float] | None] 33 xticks: NotRequired[list[float] | None] 34 yticks: NotRequired[list[float] | None] 35 x_scale: NotRequired[str | None] 36 y_scale: NotRequired[str | None] 37 # --- splat options 38 legend: NotRequired[bool | dict[str, Any] | None] 39 axhspan: NotRequired[dict[str, Any]] 40 axvspan: NotRequired[dict[str, Any]] 41 axhline: NotRequired[dict[str, Any]] 42 axvline: NotRequired[dict[str, Any]] 43 # --- options for annotations 44 lfooter: NotRequired[str] 45 rfooter: NotRequired[str] 46 lheader: NotRequired[str] 47 rheader: NotRequired[str] 48 # --- file/save options 49 pre_tag: NotRequired[str] 50 tag: NotRequired[str] 51 chart_dir: NotRequired[str] 52 file_type: NotRequired[str] 53 dpi: NotRequired[int] 54 figsize: NotRequired[tuple[float, float]] 55 show: NotRequired[bool] 56 # --- other options 57 preserve_lims: NotRequired[bool] 58 remove_legend: NotRequired[bool] 59 zero_y: NotRequired[bool] 60 y0: NotRequired[bool] 61 x0: NotRequired[bool] 62 dont_save: NotRequired[bool] 63 dont_close: NotRequired[bool]
Keyword arguments for the finalise_plot function.
80def bar_plot_finalise( 81 data: DataT, 82 **kwargs: Unpack[BPFKwargs], 83) -> None: 84 """ 85 A convenience function to call bar_plot() and finalise_plot(). 86 """ 87 validate_kwargs(schema=BPFKwargs, caller="bar_plot_finalise", **kwargs) 88 impose_legend(data=data, kwargs=kwargs) 89 plot_then_finalise( 90 data, 91 function=bar_plot, 92 **kwargs, 93 )
A convenience function to call bar_plot() and finalise_plot().
64def line_plot_finalise( 65 data: DataT, 66 **kwargs: Unpack[LPFKwargs], 67) -> None: 68 """ 69 A convenience function to call line_plot() then finalise_plot(). 70 """ 71 validate_kwargs(schema=LPFKwargs, caller="line_plot_finalise", **kwargs) 72 impose_legend(data=data, kwargs=kwargs) 73 plot_then_finalise(data, function=line_plot, **kwargs)
A convenience function to call line_plot() then finalise_plot().
116def postcovid_plot_finalise( 117 data: DataT, 118 **kwargs: Unpack[PCFKwargs], 119) -> None: 120 """ 121 A convenience function to call postcovid_plot() and finalise_plot(). 122 """ 123 validate_kwargs(schema=PCFKwargs, caller="postcovid_plot_finalise", **kwargs) 124 impose_legend(force=True, kwargs=kwargs) 125 plot_then_finalise(data, function=postcovid_plot, **kwargs)
A convenience function to call postcovid_plot() and finalise_plot().
177def growth_plot_finalise(data: DataT, **kwargs: Unpack[GrowthPFKwargs]) -> None: 178 """ 179 A convenience function to call series_growth_plot() and finalise_plot(). 180 Use this when you are providing the raw growth data. Don't forget to 181 set the ylabel in kwargs. 182 """ 183 validate_kwargs(schema=GrowthPFKwargs, caller="growth_plot_finalise", **kwargs) 184 impose_legend(force=True, kwargs=kwargs) 185 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.
132def revision_plot_finalise( 133 data: DataT, 134 **kwargs: Unpack[RevPFKwargs], 135) -> None: 136 """ 137 A convenience function to call revision_plot() and finalise_plot(). 138 """ 139 validate_kwargs(schema=RevPFKwargs, caller="revision_plot_finalise", **kwargs) 140 impose_legend(force=True, kwargs=kwargs) 141 plot_then_finalise(data=data, function=revision_plot, **kwargs)
A convenience function to call revision_plot() and finalise_plot().
148def run_plot_finalise( 149 data: DataT, 150 **kwargs: Unpack[RunPFKwargs], 151) -> None: 152 """ 153 A convenience function to call run_plot() and finalise_plot(). 154 """ 155 validate_kwargs(schema=RunPFKwargs, caller="run_plot_finalise", **kwargs) 156 impose_legend(force=True, kwargs=kwargs) 157 plot_then_finalise(data=data, function=run_plot, **kwargs)
A convenience function to call run_plot() and finalise_plot().
100def seastrend_plot_finalise( 101 data: DataT, 102 **kwargs: Unpack[SFKwargs], 103) -> None: 104 """ 105 A convenience function to call seas_trend_plot() and finalise_plot(). 106 """ 107 validate_kwargs(schema=SFKwargs, caller="seastrend_plot_finalise", **kwargs) 108 impose_legend(force=True, kwargs=kwargs) 109 plot_then_finalise(data, function=seastrend_plot, **kwargs)
A convenience function to call seas_trend_plot() and finalise_plot().
164def series_growth_plot_finalise(data: DataT, **kwargs: Unpack[SGFPKwargs]) -> None: 165 """ 166 A convenience function to call series_growth_plot() and finalise_plot(). 167 """ 168 validate_kwargs(schema=SGFPKwargs, caller="series_growth_plot_finalise", **kwargs) 169 impose_legend(force=True, kwargs=kwargs) 170 plot_then_finalise(data=data, function=series_growth_plot, **kwargs)
A convenience function to call series_growth_plot() and finalise_plot().
192def summary_plot_finalise( 193 data: DataT, 194 **kwargs: Unpack[SumPFKwargs], 195) -> None: 196 """ 197 A convenience function to call summary_plot() and finalise_plot(). 198 This is more complex than most of the above convienience methods. 199 200 Arguments 201 - data: DataFrame containing the summary data. The index must be a PeriodIndex. 202 - kwargs: additional arguments for the plot 203 """ 204 205 # --- standard arguments 206 if not isinstance(data, DataFrame) and isinstance(data.index, PeriodIndex): 207 raise TypeError("Data must be a DataFrame with a PeriodIndex.") 208 validate_kwargs(schema=SumPFKwargs, caller="summary_plot_finalise", **kwargs) 209 kwargs["title"] = kwargs.get("title", f"Summary at {data.index[-1].strftime('%b-%Y')}") 210 kwargs["preserve_lims"] = kwargs.get("preserve_lims", True) 211 212 start: int | Period | None = kwargs.get("plot_from", 0) 213 if start is None: 214 start = data.index[0] 215 if isinstance(start, int): 216 start = data.index[start] 217 kwargs["plot_from"] = start 218 if not isinstance(start, Period): 219 raise TypeError("plot_from must be a Period or convertible to one") 220 221 pre_tag: str = kwargs.get("pre_tag", "") 222 for plot_type in ("zscores", "zscaled"): 223 # some sorting of kwargs for plot production 224 kwargs["plot_type"] = plot_type 225 kwargs["pre_tag"] = pre_tag + plot_type 226 227 if plot_type == "zscores": 228 kwargs["xlabel"] = f"Z-scores for prints since {start.strftime('%b-%Y')}" 229 kwargs["x0"] = True 230 else: 231 kwargs["xlabel"] = f"-1 to 1 scaled z-scores since {start.strftime('%b-%Y')}" 232 kwargs.pop("x0", None) 233 234 plot_then_finalise( 235 data, 236 function=summary_plot, 237 **kwargs, 238 )
A convenience function to call summary_plot() and finalise_plot(). This is more complex than most of the above convienience methods.
Arguments
- data: DataFrame containing the summary data. The index must be a PeriodIndex.
- kwargs: additional arguments for the plot