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
104def get_setting(setting: Settings) -> Any: 105 """ 106 Get a setting from the global settings. 107 108 Arguments: 109 - setting: str - name of the setting to get. The possible settings are: 110 - file_type: str - the file type to use for saving plots 111 - figsize: tuple[float, float] - the figure size to use for plots 112 - file_dpi: int - the DPI to use for saving plots 113 - line_narrow: float - the line width for narrow lines 114 - line_normal: float - the line width for normal lines 115 - line_wide: float - the line width for wide lines 116 - bar_width: float - the width of bars in bar plots 117 - legend_font_size: float | str - the font size for legends 118 - legend: dict[str, Any] - the legend settings 119 - colors: dict[int, list[str]] - a dictionary of colors for 120 different numbers of lines 121 - chart_dir: str - the directory to save charts in 122 123 Raises: 124 - KeyError: if the setting is not found 125 126 Returns: 127 - value: Any - the value of the setting 128 """ 129 if setting not in mgplot_defaults: 130 raise KeyError(f"Setting '{setting}' not found in mgplot_defaults.") 131 return mgplot_defaults[setting]
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
134def set_setting(setting: Settings, value: Any) -> None: 135 """ 136 Set a setting in the global settings. 137 Raises KeyError if the setting is not found. 138 139 Arguments: 140 - setting: str - name of the setting to set (see get_setting()) 141 - value: Any - the value to set the setting to 142 """ 143 144 if setting not in DefaultTypes.__required_keys__: 145 raise KeyError(f"Setting '{setting}' not found in mgplot_defaults.") 146 mgplot_defaults[setting] = value
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
165def set_chart_dir(chart_dir: str) -> None: 166 """ 167 A function to set a global chart directory for finalise_plot(), 168 so that it does not need to be included as an argument in each 169 call to finalise_plot(). Create the directory if it does not exist. 170 171 Note: Path.mkdir() may raise an exception if a directory cannot be created. 172 173 Note: This is a wrapper for set_setting() to set the chart_dir setting, and 174 create the directory if it does not exist. 175 176 Arguments: 177 - chart_dir: str - the directory to set as the chart directory 178 """ 179 180 if not chart_dir: 181 chart_dir = "." # avoid the empty string 182 Path(chart_dir).mkdir(parents=True, exist_ok=True) 183 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
149def clear_chart_dir() -> None: 150 """ 151 Remove all graph-image files from the global chart_dir. 152 This is a convenience function to remove all files from the 153 chart_dir directory. It does not remove the directory itself. 154 Note: the function creates the directory if it does not exist. 155 """ 156 157 chart_dir = get_setting("chart_dir") 158 Path(chart_dir).mkdir(parents=True, exist_ok=True) 159 for ext in ("png", "svg", "jpg", "jpeg"): 160 for fs_object in Path(chart_dir).glob(f"*.{ext}"): 161 if fs_object.is_file(): 162 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.
250def summary_plot(data: DataT, **kwargs: Unpack[SummaryKwargs]) -> Axes: 251 """Plot a summary of historical data for a given DataFrame. 252 253 Args:x 254 - summary: DataFrame containing the summary data. The column names are 255 used as labels for the plot. 256 - kwargs: additional arguments for the plot, including: 257 258 Returns Axes. 259 """ 260 261 # --- check the kwargs 262 me = "summary_plot" 263 report_kwargs(caller=me, **kwargs) 264 validate_kwargs(schema=SummaryKwargs, caller=me, **kwargs) 265 266 # --- check the data 267 data = check_clean_timeseries(data, me) 268 if not isinstance(data, DataFrame): 269 raise TypeError("data must be a pandas DataFrame for summary_plot()") 270 df = DataFrame(data) # syntactic sugar for type hinting 271 272 # --- legend 273 kwargs["legend"] = kwargs.get( 274 "legend", 275 { 276 # put the legend below the x-axis label 277 "loc": "upper center", 278 "fontsize": "xx-small", 279 "bbox_to_anchor": (0.5, -0.125), 280 "ncol": 4, 281 }, 282 ) 283 284 # --- and plot it ... 285 ax, plot_type = plot_the_data(df, **kwargs) 286 label_x_axis( 287 kwargs.get("plot_from", 0), label=kwargs.get("xlabel", ""), plot_type=plot_type, ax=ax, df=df 288 ) 289 mark_reference_lines(plot_type, ax) 290 291 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] 44 legend: NotRequired[dict[str, Any]] 45 xlabel: NotRequired[str | None]
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.
253def finalise_plot(axes: Axes, **kwargs: Unpack[FinaliseKwargs]) -> None: 254 """ 255 A function to finalise and save plots to the file system. The filename 256 for the saved plot is constructed from the global chart_dir, the plot's title, 257 any specified tag text, and the file_type for the plot. 258 259 Arguments: 260 - axes - matplotlib axes object - required 261 - kwargs: FinaliseKwargs 262 263 Returns: 264 - None 265 """ 266 267 # --- check the kwargs 268 me = "finalise_plot" 269 report_kwargs(caller=me, **kwargs) 270 validate_kwargs(schema=FinaliseKwargs, caller=me, **kwargs) 271 272 # --- sanity checks 273 if len(axes.get_children()) < 1: 274 print("Warning: finalise_plot() called with empty axes, which was ignored.") 275 return 276 277 # --- remember axis-limits should we need to restore thems 278 xlim, ylim = axes.get_xlim(), axes.get_ylim() 279 280 # margins 281 axes.margins(0.02) 282 axes.autoscale(tight=False) # This is problematic ... 283 284 apply_kwargs(axes, **kwargs) 285 286 # tight layout and save the figure 287 fig = axes.figure 288 if "preserve_lims" in kwargs and kwargs["preserve_lims"]: 289 # restore the original limits of the axes 290 axes.set_xlim(xlim) 291 axes.set_ylim(ylim) 292 if not isinstance(fig, mpl.figure.SubFigure): # mypy 293 fig.tight_layout(pad=1.1) 294 apply_late_kwargs(axes, **kwargs) 295 legend = axes.get_legend() 296 if legend and kwargs.get("remove_legend", False): 297 legend.remove() 298 if not isinstance(fig, mpl.figure.SubFigure): # mypy 299 save_to_file(fig, **kwargs) 300 301 # show the plot in Jupyter Lab 302 if "show" in kwargs and kwargs["show"]: 303 plt.show() 304 305 # And close 306 closing = True if "dont_close" not in kwargs else not kwargs["dont_close"] 307 if closing: 308 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.
81def bar_plot_finalise( 82 data: DataT, 83 **kwargs: Unpack[BPFKwargs], 84) -> None: 85 """ 86 A convenience function to call bar_plot() and finalise_plot(). 87 """ 88 validate_kwargs(schema=BPFKwargs, caller="bar_plot_finalise", **kwargs) 89 impose_legend(data=data, kwargs=kwargs) 90 plot_then_finalise( 91 data, 92 function=bar_plot, 93 **kwargs, 94 )
A convenience function to call bar_plot() and finalise_plot().
65def line_plot_finalise( 66 data: DataT, 67 **kwargs: Unpack[LPFKwargs], 68) -> None: 69 """ 70 A convenience function to call line_plot() then finalise_plot(). 71 """ 72 validate_kwargs(schema=LPFKwargs, caller="line_plot_finalise", **kwargs) 73 impose_legend(data=data, kwargs=kwargs) 74 plot_then_finalise(data, function=line_plot, **kwargs)
A convenience function to call line_plot() then finalise_plot().
117def postcovid_plot_finalise( 118 data: DataT, 119 **kwargs: Unpack[PCFKwargs], 120) -> None: 121 """ 122 A convenience function to call postcovid_plot() and finalise_plot(). 123 """ 124 validate_kwargs(schema=PCFKwargs, caller="postcovid_plot_finalise", **kwargs) 125 impose_legend(force=True, kwargs=kwargs) 126 plot_then_finalise(data, function=postcovid_plot, **kwargs)
A convenience function to call postcovid_plot() and finalise_plot().
178def growth_plot_finalise(data: DataT, **kwargs: Unpack[GrowthPFKwargs]) -> None: 179 """ 180 A convenience function to call series_growth_plot() and finalise_plot(). 181 Use this when you are providing the raw growth data. Don't forget to 182 set the ylabel in kwargs. 183 """ 184 validate_kwargs(schema=GrowthPFKwargs, caller="growth_plot_finalise", **kwargs) 185 impose_legend(force=True, kwargs=kwargs) 186 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.
133def revision_plot_finalise( 134 data: DataT, 135 **kwargs: Unpack[RevPFKwargs], 136) -> None: 137 """ 138 A convenience function to call revision_plot() and finalise_plot(). 139 """ 140 validate_kwargs(schema=RevPFKwargs, caller="revision_plot_finalise", **kwargs) 141 impose_legend(force=True, kwargs=kwargs) 142 plot_then_finalise(data=data, function=revision_plot, **kwargs)
A convenience function to call revision_plot() and finalise_plot().
149def run_plot_finalise( 150 data: DataT, 151 **kwargs: Unpack[RunPFKwargs], 152) -> None: 153 """ 154 A convenience function to call run_plot() and finalise_plot(). 155 """ 156 validate_kwargs(schema=RunPFKwargs, caller="run_plot_finalise", **kwargs) 157 impose_legend(force=True, kwargs=kwargs) 158 plot_then_finalise(data=data, function=run_plot, **kwargs)
A convenience function to call run_plot() and finalise_plot().
101def seastrend_plot_finalise( 102 data: DataT, 103 **kwargs: Unpack[SFKwargs], 104) -> None: 105 """ 106 A convenience function to call seas_trend_plot() and finalise_plot(). 107 """ 108 validate_kwargs(schema=SFKwargs, caller="seastrend_plot_finalise", **kwargs) 109 impose_legend(force=True, kwargs=kwargs) 110 plot_then_finalise(data, function=seastrend_plot, **kwargs)
A convenience function to call seas_trend_plot() and finalise_plot().
165def series_growth_plot_finalise(data: DataT, **kwargs: Unpack[SGFPKwargs]) -> None: 166 """ 167 A convenience function to call series_growth_plot() and finalise_plot(). 168 """ 169 validate_kwargs(schema=SGFPKwargs, caller="series_growth_plot_finalise", **kwargs) 170 impose_legend(force=True, kwargs=kwargs) 171 plot_then_finalise(data=data, function=series_growth_plot, **kwargs)
A convenience function to call series_growth_plot() and finalise_plot().
193def summary_plot_finalise( 194 data: DataT, 195 **kwargs: Unpack[SumPFKwargs], 196) -> None: 197 """ 198 A convenience function to call summary_plot() and finalise_plot(). 199 This is more complex than most of the above convienience methods. 200 201 Arguments 202 - data: DataFrame containing the summary data. The index must be a PeriodIndex. 203 - kwargs: additional arguments for the plot 204 """ 205 206 # --- standard arguments 207 if not isinstance(data, DataFrame) and isinstance(data.index, PeriodIndex): 208 raise TypeError("Data must be a DataFrame with a PeriodIndex.") 209 validate_kwargs(schema=SumPFKwargs, caller="summary_plot_finalise", **kwargs) 210 kwargs["title"] = kwargs.get("title", f"Summary at {label_period(data.index[-1])}") 211 kwargs["preserve_lims"] = kwargs.get("preserve_lims", True) 212 213 start: int | Period | None = kwargs.get("plot_from", 0) 214 if start is None: 215 start = data.index[0] 216 if isinstance(start, int): 217 start = data.index[start] 218 kwargs["plot_from"] = start 219 if not isinstance(start, Period): 220 raise TypeError("plot_from must be a Period or convertible to one") 221 222 pre_tag: str = kwargs.get("pre_tag", "") 223 for plot_type in ("zscores", "zscaled"): 224 # some sorting of kwargs for plot production 225 kwargs["plot_type"] = plot_type 226 kwargs["pre_tag"] = pre_tag + plot_type 227 228 plot_then_finalise( 229 data, 230 function=summary_plot, 231 **kwargs, 232 )
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