mgplot.line_plot

line_plot.py: Plot a series or a dataframe with lines.

  1"""
  2line_plot.py:
  3Plot a series or a dataframe with lines.
  4"""
  5
  6# --- imports
  7from typing import Any
  8from collections.abc import Sequence
  9import matplotlib.pyplot as plt
 10from pandas import DataFrame, Period
 11
 12from mgplot.settings import DataT, get_setting
 13from mgplot.finalise_plot import make_legend
 14from mgplot.kw_type_checking import (
 15    report_kwargs,
 16    validate_kwargs,
 17    validate_expected,
 18    ExpectedTypeDict,
 19)
 20from mgplot.utilities import (
 21    apply_defaults,
 22    get_color_list,
 23    get_axes,
 24    annotate_series,
 25    constrain_data,
 26    check_clean_timeseries,
 27)
 28
 29
 30# --- constants
 31DATA = "data"
 32AX = "ax"
 33STYLE, WIDTH, COLOR, ALPHA = "style", "width", "color", "alpha"
 34ANNOTATE = "annotate"
 35ROUNDING = "rounding"
 36FONTSIZE = "fontsize"
 37DROPNA = "dropna"
 38DRAWSTYLE, MARKER, MARKERSIZE = "drawstyle", "marker", "markersize"
 39PLOT_FROM = "plot_from"  # used to constrain the data to a starting point
 40LEGEND = "legend"
 41
 42LINE_KW_TYPES: ExpectedTypeDict = {
 43    AX: (plt.Axes, type(None)),
 44    STYLE: (str, Sequence, (str,)),
 45    WIDTH: (float, int, Sequence, (float, int)),
 46    COLOR: (str, Sequence, (str,)),
 47    ALPHA: (float, Sequence, (float,)),
 48    DRAWSTYLE: (str, Sequence, (str,), type(None)),
 49    MARKER: (str, Sequence, (str,), type(None)),
 50    MARKERSIZE: (float, Sequence, (float,), int, type(None)),
 51    DROPNA: (bool, Sequence, (bool,)),
 52    ANNOTATE: (bool, Sequence, (bool,)),
 53    ROUNDING: (Sequence, (bool, int), int, bool, type(None)),
 54    FONTSIZE: (Sequence, (str, int), str, int, type(None)),
 55    PLOT_FROM: (int, Period, type(None)),
 56    LEGEND: (dict, (str, object), bool, type(None)),
 57}
 58validate_expected(LINE_KW_TYPES, "line_plot")
 59
 60
 61# --- functions
 62def _get_style_width_color_etc(
 63    item_count, num_data_points, **kwargs
 64) -> tuple[dict[str, list | tuple], dict[str, Any]]:
 65    """
 66    Get the plot-line attributes arguemnts.
 67    Returns a dictionary of lists of attributes for each line, and
 68    a modified kwargs dictionary.
 69    """
 70
 71    data_point_thresh = 151
 72    defaults: dict[str, Any] = {
 73        STYLE: "-",
 74        WIDTH: (
 75            get_setting("line_normal")
 76            if num_data_points > data_point_thresh
 77            else get_setting("line_wide")
 78        ),
 79        COLOR: kwargs.get(COLOR, get_color_list(item_count)),
 80        ALPHA: 1.0,
 81        DRAWSTYLE: None,
 82        MARKER: None,
 83        MARKERSIZE: 10,
 84        DROPNA: True,
 85        ANNOTATE: False,
 86        ROUNDING: True,
 87        FONTSIZE: "small",
 88    }
 89
 90    return apply_defaults(item_count, defaults, kwargs)
 91
 92
 93def line_plot(data: DataT, **kwargs) -> plt.Axes:
 94    """
 95    Build a single plot from the data passed in.
 96    This can be a single- or multiple-line plot.
 97    Return the axes object for the build.
 98
 99    Agruments:
100    - data: DataFrame | Series - data to plot
101    - kwargs:
102        - ax: plt.Axes | None - axes to plot on (optional)
103        - dropna: bool | list[bool] - whether to delete NAs frm the
104          data before plotting [optional]
105        - color: str | list[str] - line colors.
106        - width: float | list[float] - line widths [optional].
107        - style: str | list[str] - line styles [optional].
108        - alpha: float | list[float] - line transparencies [optional].
109        - marker: str | list[str] - line markers [optional].
110        - marker_size: float | list[float] - line marker sizes [optional].
111        - annotate: bool | list[bool] - whether to annotate a series.
112        - rounding: int | bool | list[int | bool] - number of decimal places
113          to round an annotation. If True, a default between 0 and 2 is
114          used.
115        - fontsize: int | str | list[int | str] - font size for the
116          annotation.
117        - drawstyle: str | list[str] - matplotlib line draw styles.
118
119    Returns:
120    - axes: plt.Axes - the axes object for the plot
121    """
122
123    # --- check the kwargs
124    me = "line_plot"
125    report_kwargs(called_from=me, **kwargs)
126    validate_kwargs(LINE_KW_TYPES, me, **kwargs)
127
128    # --- check the data
129    data = check_clean_timeseries(data, me)
130    df = DataFrame(data)  # really we are only plotting DataFrames
131    df, kwargs = constrain_data(df, **kwargs)
132
133    # --- some special defaults
134    if len(df.columns) > 1:
135        # default to displaying a legend
136        kwargs["legend"] = kwargs.get("legend", True)
137    if len(df.columns) > 4:
138        # default to using a style for the lines
139        kwargs["style"] = kwargs.get("style", ["solid", "dashed", "dashdot", "dotted"])
140
141    # --- Let's plot
142    axes, kwargs = get_axes(**kwargs)  # get the axes to plot on
143    if df.empty or df.isna().all().all():
144        # Note: finalise plot will ignore an empty axes object
145        print("Warning: No data to plot.")
146        return axes
147
148    # --- get the arguments for each line we will plot ...
149    item_count = len(df.columns)
150    num_data_points = len(df)
151    swce, kwargs = _get_style_width_color_etc(item_count, num_data_points, **kwargs)
152
153    for i, column in enumerate(df.columns):
154        series = df[column]
155        series = series.dropna() if DROPNA in swce and swce[DROPNA][i] else series
156        if series.empty or series.isna().all():
157            continue
158
159        axes = series.plot(
160            ls=swce[STYLE][i],
161            lw=swce[WIDTH][i],
162            color=swce[COLOR][i],
163            alpha=swce[ALPHA][i],
164            marker=swce[MARKER][i],
165            ms=swce[MARKERSIZE][i],
166            drawstyle=swce[DRAWSTYLE][i],
167            ax=axes,
168        )
169
170        if swce[ANNOTATE][i] is None or not swce[ANNOTATE][i]:
171            continue
172
173        annotate_series(
174            series,
175            axes,
176            rounding=swce[ROUNDING][i],
177            color=swce[COLOR][i],
178            fontsize=swce[FONTSIZE][i],
179        )
180
181    # add a legend if requested
182    if len(df.columns) > 1:
183        kwargs[LEGEND] = kwargs.get(LEGEND, get_setting("legend"))
184
185    if LEGEND in kwargs:
186        make_legend(axes, kwargs[LEGEND])
187
188    return axes
DATA = 'data'
AX = 'ax'
ANNOTATE = 'annotate'
ROUNDING = 'rounding'
FONTSIZE = 'fontsize'
DROPNA = 'dropna'
PLOT_FROM = 'plot_from'
LEGEND = 'legend'
LINE_KW_TYPES: mgplot.kw_type_checking.ExpectedTypeDict = {'ax': (<class 'matplotlib.axes._axes.Axes'>, <class 'NoneType'>), 'style': (<class 'str'>, <class 'collections.abc.Sequence'>, (<class 'str'>,)), 'width': (<class 'float'>, <class 'int'>, <class 'collections.abc.Sequence'>, (<class 'float'>, <class 'int'>)), 'color': (<class 'str'>, <class 'collections.abc.Sequence'>, (<class 'str'>,)), 'alpha': (<class 'float'>, <class 'collections.abc.Sequence'>, (<class 'float'>,)), 'drawstyle': (<class 'str'>, <class 'collections.abc.Sequence'>, (<class 'str'>,), <class 'NoneType'>), 'marker': (<class 'str'>, <class 'collections.abc.Sequence'>, (<class 'str'>,), <class 'NoneType'>), 'markersize': (<class 'float'>, <class 'collections.abc.Sequence'>, (<class 'float'>,), <class 'int'>, <class 'NoneType'>), 'dropna': (<class 'bool'>, <class 'collections.abc.Sequence'>, (<class 'bool'>,)), 'annotate': (<class 'bool'>, <class 'collections.abc.Sequence'>, (<class 'bool'>,)), 'rounding': (<class 'collections.abc.Sequence'>, (<class 'bool'>, <class 'int'>), <class 'int'>, <class 'bool'>, <class 'NoneType'>), 'fontsize': (<class 'collections.abc.Sequence'>, (<class 'str'>, <class 'int'>), <class 'str'>, <class 'int'>, <class 'NoneType'>), 'plot_from': (<class 'int'>, <class 'pandas._libs.tslibs.period.Period'>, <class 'NoneType'>), 'legend': (<class 'dict'>, (<class 'str'>, <class 'object'>), <class 'bool'>, <class 'NoneType'>)}
def line_plot(data: ~DataT, **kwargs) -> matplotlib.axes._axes.Axes:
 94def line_plot(data: DataT, **kwargs) -> plt.Axes:
 95    """
 96    Build a single plot from the data passed in.
 97    This can be a single- or multiple-line plot.
 98    Return the axes object for the build.
 99
100    Agruments:
101    - data: DataFrame | Series - data to plot
102    - kwargs:
103        - ax: plt.Axes | None - axes to plot on (optional)
104        - dropna: bool | list[bool] - whether to delete NAs frm the
105          data before plotting [optional]
106        - color: str | list[str] - line colors.
107        - width: float | list[float] - line widths [optional].
108        - style: str | list[str] - line styles [optional].
109        - alpha: float | list[float] - line transparencies [optional].
110        - marker: str | list[str] - line markers [optional].
111        - marker_size: float | list[float] - line marker sizes [optional].
112        - annotate: bool | list[bool] - whether to annotate a series.
113        - rounding: int | bool | list[int | bool] - number of decimal places
114          to round an annotation. If True, a default between 0 and 2 is
115          used.
116        - fontsize: int | str | list[int | str] - font size for the
117          annotation.
118        - drawstyle: str | list[str] - matplotlib line draw styles.
119
120    Returns:
121    - axes: plt.Axes - the axes object for the plot
122    """
123
124    # --- check the kwargs
125    me = "line_plot"
126    report_kwargs(called_from=me, **kwargs)
127    validate_kwargs(LINE_KW_TYPES, me, **kwargs)
128
129    # --- check the data
130    data = check_clean_timeseries(data, me)
131    df = DataFrame(data)  # really we are only plotting DataFrames
132    df, kwargs = constrain_data(df, **kwargs)
133
134    # --- some special defaults
135    if len(df.columns) > 1:
136        # default to displaying a legend
137        kwargs["legend"] = kwargs.get("legend", True)
138    if len(df.columns) > 4:
139        # default to using a style for the lines
140        kwargs["style"] = kwargs.get("style", ["solid", "dashed", "dashdot", "dotted"])
141
142    # --- Let's plot
143    axes, kwargs = get_axes(**kwargs)  # get the axes to plot on
144    if df.empty or df.isna().all().all():
145        # Note: finalise plot will ignore an empty axes object
146        print("Warning: No data to plot.")
147        return axes
148
149    # --- get the arguments for each line we will plot ...
150    item_count = len(df.columns)
151    num_data_points = len(df)
152    swce, kwargs = _get_style_width_color_etc(item_count, num_data_points, **kwargs)
153
154    for i, column in enumerate(df.columns):
155        series = df[column]
156        series = series.dropna() if DROPNA in swce and swce[DROPNA][i] else series
157        if series.empty or series.isna().all():
158            continue
159
160        axes = series.plot(
161            ls=swce[STYLE][i],
162            lw=swce[WIDTH][i],
163            color=swce[COLOR][i],
164            alpha=swce[ALPHA][i],
165            marker=swce[MARKER][i],
166            ms=swce[MARKERSIZE][i],
167            drawstyle=swce[DRAWSTYLE][i],
168            ax=axes,
169        )
170
171        if swce[ANNOTATE][i] is None or not swce[ANNOTATE][i]:
172            continue
173
174        annotate_series(
175            series,
176            axes,
177            rounding=swce[ROUNDING][i],
178            color=swce[COLOR][i],
179            fontsize=swce[FONTSIZE][i],
180        )
181
182    # add a legend if requested
183    if len(df.columns) > 1:
184        kwargs[LEGEND] = kwargs.get(LEGEND, get_setting("legend"))
185
186    if LEGEND in kwargs:
187        make_legend(axes, kwargs[LEGEND])
188
189    return axes

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

Agruments:

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

Returns:

  • axes: plt.Axes - the axes object for the plot