Skip to content

pandas_openscm.plotting#

Plotting

Classes:

Name Description
MissingQuantileError

Raised when a quantile(s) is missing from a pd.DataFrame

PlumePlotter

Object which is able to plot plume plots

SingleLinePlotter

Object which is able to plot single lines

SinglePlumePlotter

Object which is able to plot single plumes

Functions:

Name Description
create_legend_default

Create legend, default implementation

extract_single_unit

Extract the unit of the data, expecting there to only be one unit

fill_out_dashes

Fill out dashes

fill_out_palette

Fill out a palette

get_default_colour_cycler

Get the default colour cycler

get_default_dash_cycler

Get the default dash cycler

get_pdf_from_pre_calculated

Get a pd.DataFrame for plotting from pre-calculated quantiles

get_quantiles

Get just the quantiles from a QUANTILES_PLUMES_LIKE

get_values_line

Get values for plotting a line

get_values_plume

Get values for plotting a line

plot_plume_after_calculating_quantiles_func

Plot a plume plot, calculating the required quantiles first

plot_plume_func

Plot a plume plot

same_shape_as_x_vals

Validate that the received values are the same shape as obj.x_vals

Attributes:

Name Type Description
COLOUR_VALUE_LIKE TypeAlias

Type that allows a colour to be specified in matplotlib

DASH_VALUE_LIKE TypeAlias

Types that allow a dash to be specified in matplotlib

PALETTE_LIKE TypeAlias

Palette-like type

QUANTILES_PLUMES_LIKE TypeAlias

Type that quantiles and the alpha to use for plotting their line/plume

COLOUR_VALUE_LIKE module-attribute #

Type that allows a colour to be specified in matplotlib

DASH_VALUE_LIKE module-attribute #

DASH_VALUE_LIKE: TypeAlias = Union[
    str, tuple[float, tuple[float, ...]]
]

Types that allow a dash to be specified in matplotlib

PALETTE_LIKE module-attribute #

PALETTE_LIKE: TypeAlias = Mapping[T, COLOUR_VALUE_LIKE]

Palette-like type

QUANTILES_PLUMES_LIKE module-attribute #

QUANTILES_PLUMES_LIKE: TypeAlias = tuple[
    Union[
        tuple[float, float],
        tuple[tuple[float, float], float],
    ],
    ...,
]

Type that quantiles and the alpha to use for plotting their line/plume

MissingQuantileError #

Bases: KeyError

Raised when a quantile(s) is missing from a pd.DataFrame

Methods:

Name Description
__init__

Initialise the error

Source code in src/pandas_openscm/plotting.py
class MissingQuantileError(KeyError):
    """
    Raised when a quantile(s) is missing from a [pd.DataFrame][pandas.DataFrame]
    """

    def __init__(
        self,
        available_quantiles: Collection[float],
        missing_quantiles: Collection[float],
    ) -> None:
        """
        Initialise the error

        Parameters
        ----------
        available_quantiles
            Available quantiles

        missing_quantiles
            Missing quantiles
        """
        error_msg = (
            f"The folllowing quantiles are missing: {missing_quantiles=}. "
            f"{available_quantiles=}"
        )
        super().__init__(error_msg)

__init__ #

__init__(
    available_quantiles: Collection[float],
    missing_quantiles: Collection[float],
) -> None

Initialise the error

Parameters:

Name Type Description Default
available_quantiles Collection[float]

Available quantiles

required
missing_quantiles Collection[float]

Missing quantiles

required
Source code in src/pandas_openscm/plotting.py
def __init__(
    self,
    available_quantiles: Collection[float],
    missing_quantiles: Collection[float],
) -> None:
    """
    Initialise the error

    Parameters
    ----------
    available_quantiles
        Available quantiles

    missing_quantiles
        Missing quantiles
    """
    error_msg = (
        f"The folllowing quantiles are missing: {missing_quantiles=}. "
        f"{available_quantiles=}"
    )
    super().__init__(error_msg)

PlumePlotter #

Object which is able to plot plume plots

Methods:

Name Description
from_df

Initialise from a pd.DataFrame

generate_legend_handles

Generate handles for the legend

plot

Plot

Attributes:

Name Type Description
dashes dict[Any, str | tuple[float, tuple[float, ...]]] | None

Dashes used for plotting different values of the style variable

hue_var_label str

Label for the hue variable in the legend

lines Iterable[SingleLinePlotter]

Lines to plot

palette PALETTE_LIKE[Any]

Palette used for plotting different values of the hue variable

plumes Iterable[SinglePlumePlotter]

Lines to plot

quantile_var_label str

Label for the quantile variable in the legend

style_var_label str | None

Label for the style variable in the legend (if not None)

x_label str | None

Label to apply to the x-axis (if None, no label is applied)

y_label str | None

Label to apply to the y-axis (if None, no label is applied)

Source code in src/pandas_openscm/plotting.py
 833
 834
 835
 836
 837
 838
 839
 840
 841
 842
 843
 844
 845
 846
 847
 848
 849
 850
 851
 852
 853
 854
 855
 856
 857
 858
 859
 860
 861
 862
 863
 864
 865
 866
 867
 868
 869
 870
 871
 872
 873
 874
 875
 876
 877
 878
 879
 880
 881
 882
 883
 884
 885
 886
 887
 888
 889
 890
 891
 892
 893
 894
 895
 896
 897
 898
 899
 900
 901
 902
 903
 904
 905
 906
 907
 908
 909
 910
 911
 912
 913
 914
 915
 916
 917
 918
 919
 920
 921
 922
 923
 924
 925
 926
 927
 928
 929
 930
 931
 932
 933
 934
 935
 936
 937
 938
 939
 940
 941
 942
 943
 944
 945
 946
 947
 948
 949
 950
 951
 952
 953
 954
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
@define
class PlumePlotter:
    """Object which is able to plot plume plots"""

    lines: Iterable[SingleLinePlotter]
    """Lines to plot"""

    plumes: Iterable[SinglePlumePlotter]
    """Lines to plot"""

    hue_var_label: str
    """Label for the hue variable in the legend"""

    style_var_label: str | None
    """Label for the style variable in the legend (if not `None`)"""

    quantile_var_label: str
    """Label for the quantile variable in the legend"""

    palette: PALETTE_LIKE[Any]
    """Palette used for plotting different values of the hue variable"""

    dashes: dict[Any, str | tuple[float, tuple[float, ...]]] | None
    """Dashes used for plotting different values of the style variable"""

    x_label: str | None
    """Label to apply to the x-axis (if `None`, no label is applied)"""

    y_label: str | None
    """Label to apply to the y-axis (if `None`, no label is applied)"""

    @classmethod
    def from_df(  # noqa: PLR0912, PLR0913, PLR0915 # object creation code is the worst
        cls,
        df: pd.DataFrame,
        *,
        quantiles_plumes: QUANTILES_PLUMES_LIKE = (
            (0.5, 0.7),
            ((0.05, 0.95), 0.2),
        ),
        quantile_var: str = "quantile",
        quantile_var_label: str | None = None,
        quantile_legend_round: int = 2,
        hue_var: str = "scenario",
        hue_var_label: str | None = None,
        palette: PALETTE_LIKE[Any] | None = None,
        warn_on_palette_value_missing: bool = True,
        style_var: str | None = "variable",
        style_var_label: str | None = None,
        dashes: dict[Any, str | tuple[float, tuple[float, ...]]] | None = None,
        warn_on_dashes_value_missing: bool = True,
        linewidth: float = 3.0,
        unit_var: str | None = "unit",
        unit_aware: bool | pint.UnitRegistry = False,
        time_units: str | None = None,
        x_label: str | None = "time",
        y_label: str | bool | None = True,
        warn_infer_y_label_with_multi_unit: bool = True,
        observed: bool = True,
    ) -> PlumePlotter:
        """
        Initialise from a [pd.DataFrame][pandas.DataFrame]

        Parameters
        ----------
        df
            [pd.DataFrame][pandas.DataFrame] from which to initialise

        quantiles_plumes
            Quantiles to plot in each plume.

            If the first element of each tuple is a tuple,
            a [SinglePlumePlotter][(m).] object will be created.
            Otherwise, if the first element is a plain float,
            a [SingleLinePlotter][(m).] object will be created.

        quantile_var
            Variable/column in the multi-index which stores information
            about the quantile that each timeseries represents.

        quantile_var_label
            Label to use as the header for the quantile section in the legend

        quantile_legend_round
            Rounding to apply to quantile values when creating the legend

        hue_var
            Variable to use for grouping data into different colour groups

        hue_var_label
            Label to use as the header for the hue/colour section in the legend

        palette
            Colour to use for the different groups in the data.

            If any groups are not included in `palette`,
            they are auto-filled.

        warn_on_palette_value_missing
            Should a warning be emitted if there are values missing from `palette`?

        style_var
            Variable to use for grouping data into different (line)style groups

        style_var_label
            Label to use as the header for the style section in the legend

        dashes
            Dash/linestyle to use for the different groups in the data.

            If any groups are not included in `dashes`,
            they are auto-filled.

        warn_on_dashes_value_missing
            Should a warning be emitted if there are values missing from `dashes`?

        linewidth
            Width to use for plotting lines.

        unit_var
            Variable/column in the multi-index which stores information
            about the unit of each timeseries.

        unit_aware
            Should the values be extracted in a unit-aware way?

            If `True`, we use the default application registry
            (retrieved with [pint.get_application_registry][]).
            Otherwise, a [pint.UnitRegistry][] can be supplied and will be used.

        time_units
            Units of the time axis of the data.

            These are required if `unit_aware` is not `False`.

        x_label
            Label to apply to the x-axis.

            If `None`, no label will be applied.

        y_label
            Label to apply to the y-axis.

            If `True`, we will try and infer the y-label based on the data's units.

            If `None`, no label will be applied.

        warn_infer_y_label_with_multi_unit
            Should a warning be raised if we try to infer the y-unit
            but the data has more than one unit?

        observed
            Passed to [pd.DataFrame.groupby][pandas.DataFrame.groupby].

        Returns
        -------
        :
             Initialised instance
        """
        if hue_var_label is None:
            hue_var_label = hue_var.capitalize()

        if style_var is not None and style_var_label is None:
            style_var_label = style_var.capitalize()

        if quantile_var_label is None:
            quantile_var_label = quantile_var.capitalize()

        infer_y_label = (
            (not unit_aware)
            and isinstance(y_label, bool)
            and y_label
            and unit_var is not None
        )

        palette_complete = fill_out_palette(
            df.index.unique(hue_var),
            palette_user_supplied=palette,
            warn_on_value_missing=warn_on_palette_value_missing,
        )

        if style_var is not None:
            group_cols = [hue_var, style_var]
            dashes_complete = fill_out_dashes(
                df.index.unique(style_var),
                dashes_user_supplied=dashes,
                warn_on_value_missing=warn_on_dashes_value_missing,
            )

        else:
            group_cols = [hue_var]
            dashes_complete = None

        lines: list[SingleLinePlotter] = []
        plumes: list[SinglePlumePlotter] = []
        values_units: list[str] = []
        for info, gdf in df.groupby(group_cols, observed=observed):
            info_d = {k: v for k, v in zip(group_cols, info)}

            colour = palette_complete[info_d[hue_var]]

            gpdf = partial(get_pdf_from_pre_calculated, gdf, quantile_col=quantile_var)

            def warn_about_missing_quantile(exc: Exception) -> None:
                warnings.warn(
                    f"Quantiles missing for {info_d}. Original exception: {exc}"
                )

            for q, alpha in quantiles_plumes:
                if not isinstance(q, tuple):
                    quantile = float(q)
                    if style_var is not None:
                        if dashes_complete is None:  # pragma: no cover
                            # should be impossible to hit this
                            raise AssertionError
                        linestyle = dashes_complete[info_d[style_var]]
                    else:
                        linestyle = "-"

                    try:
                        line_quantiles = (quantile,)
                        pdf = gpdf(quantiles=line_quantiles)
                    except MissingQuantileError as exc:
                        warn_about_missing_quantile(exc=exc)
                        continue

                    x_vals, y_vals = get_values_line(
                        pdf,
                        unit_aware=unit_aware,
                        unit_var=unit_var,
                        time_units=time_units,
                    )
                    line_plotter = SingleLinePlotter(
                        x_vals=x_vals,
                        y_vals=y_vals,
                        quantile=quantile,
                        linewidth=linewidth,
                        linestyle=linestyle,
                        color=colour,
                        alpha=alpha,
                    )
                    lines.append(line_plotter)

                else:
                    plume_quantiles = (float(q[0]), float(q[1]))
                    try:
                        pdf = gpdf(quantiles=plume_quantiles)
                    except MissingQuantileError as exc:
                        warn_about_missing_quantile(exc=exc)
                        continue

                    x_vals, y_vals_lower, y_vals_upper = get_values_plume(
                        pdf,
                        quantiles=plume_quantiles,
                        quantile_var=quantile_var,
                        unit_aware=unit_aware,
                        unit_var=unit_var,
                        time_units=time_units,
                    )
                    plume_plotter = SinglePlumePlotter(
                        x_vals=x_vals,
                        y_vals_lower=y_vals_lower,
                        y_vals_upper=y_vals_upper,
                        quantiles=plume_quantiles,
                        color=colour,
                        alpha=alpha,
                    )
                    plumes.append(plume_plotter)

                if (
                    infer_y_label
                    and unit_var is not None
                    and unit_var in pdf.index.names
                ):
                    values_units.extend(pdf.index.unique(unit_var))

        if unit_aware and isinstance(y_label, bool) and y_label:
            # Let unit-aware plotting do its thing
            y_label = None

        elif unit_var is None:
            y_label = None

        elif infer_y_label:
            if unit_var not in df.index.names:
                warnings.warn(
                    "Not auto-setting the y_label "
                    f"because {unit_var=} is not in {df.index.names=}"
                )
                y_label = None

            else:
                # Try to infer the y-label
                units_s = set(values_units)
                if len(units_s) == 1:
                    y_label = values_units[0]
                else:
                    # More than one unit plotted, don't infer a y-label
                    if warn_infer_y_label_with_multi_unit:
                        warnings.warn(
                            "Not auto-setting the y_label "
                            "because the plotted data has more than one unit: "
                            f"data units {units_s}"
                        )

                    y_label = None

        if isinstance(y_label, bool):
            msg = "y_label should have been converted before getting here"
            raise TypeError(msg)

        res = PlumePlotter(
            lines=lines,
            plumes=plumes,
            hue_var_label=hue_var_label,
            style_var_label=style_var_label,
            quantile_var_label=quantile_var_label,
            palette=palette_complete,
            dashes=dashes_complete,
            x_label=x_label,
            y_label=y_label,
        )

        return res

    def generate_legend_handles(
        self, quantile_legend_round: int = 2
    ) -> list[matplotlib.artist.Artist]:
        """
        Generate handles for the legend

        Parameters
        ----------
        quantile_legend_round
            Rounding to apply to the quantiles when creating the label

        Returns
        -------
        :
            Generated handles for the legend
        """
        try:
            import matplotlib.lines as mlines  # noqa: PLC0415
            import matplotlib.patches as mpatches  # noqa: PLC0415
        except ImportError as exc:
            raise MissingOptionalDependencyError(
                "generate_legend_handles", requirement="matplotlib"
            ) from exc

        generated_quantile_items: list[
            Union[
                tuple[float, float, str],
                tuple[tuple[float, float], float, str],
            ]
        ] = []
        quantile_items: list[matplotlib.artist.Artist] = []
        for line in self.lines:
            label = line.get_label(quantile_legend_round=quantile_legend_round)
            pid_line = (line.quantile, line.alpha, label)
            if pid_line in generated_quantile_items:
                continue

            quantile_items.append(
                mlines.Line2D([0], [0], color="k", alpha=line.alpha, label=label)
            )
            generated_quantile_items.append(pid_line)

        for plume in self.plumes:
            label = plume.get_label(quantile_legend_round=quantile_legend_round)
            pid_plume = (plume.quantiles, plume.alpha, label)
            if pid_plume in generated_quantile_items:
                continue

            quantile_items.append(
                mpatches.Patch(color="k", alpha=plume.alpha, label=label)
            )
            generated_quantile_items.append(pid_plume)

        hue_items = [
            mlines.Line2D([0], [0], color=colour, label=hue_value)
            for hue_value, colour in self.palette.items()
        ]

        legend_items = [
            mpatches.Patch(alpha=0, label=self.quantile_var_label),
            *quantile_items,
            mpatches.Patch(alpha=0, label=self.hue_var_label),
            *hue_items,
        ]
        if self.dashes is not None and self.lines:
            style_items = [
                mlines.Line2D(
                    [0],
                    [0],
                    linestyle=linestyle,
                    label=style_value,
                    color="gray",
                )
                for style_value, linestyle in self.dashes.items()
            ]
            legend_items.append(mpatches.Patch(alpha=0, label=self.style_var_label))
            legend_items.extend(style_items)

        return legend_items

    def plot(
        self,
        ax: matplotlib.axes.Axes | None = None,
        *,
        create_legend: Callable[
            [matplotlib.axes.Axes, list[matplotlib.artist.Artist]], None
        ] = create_legend_default,
        quantile_legend_round: int = 2,
    ) -> matplotlib.axes.Axes:
        """
        Plot

        Parameters
        ----------
        ax
            Axes onto which to plot

        create_legend
            Function to use to create the legend.

            This allows the user to have full control over the creation of the legend.

        quantile_legend_round
            Rounding to apply to quantile values when creating the legend

        Returns
        -------
        :
            Axes on which the data was plotted
        """
        if ax is None:
            try:
                import matplotlib.pyplot as plt  # noqa: PLC0415
            except ImportError as exc:
                raise MissingOptionalDependencyError(  # noqa: TRY003
                    "plot(ax=None, ...)", requirement="matplotlib"
                ) from exc

            _, ax = plt.subplots()

        for plume in self.plumes:
            plume.plot(ax=ax)

        for line in self.lines:
            line.plot(ax=ax)

        create_legend(
            ax,
            self.generate_legend_handles(quantile_legend_round=quantile_legend_round),
        )

        if self.x_label is not None:
            ax.set_xlabel(self.x_label)

        if self.y_label is not None:
            ax.set_ylabel(self.y_label)

        return ax

dashes instance-attribute #

dashes: (
    dict[Any, str | tuple[float, tuple[float, ...]]] | None
)

Dashes used for plotting different values of the style variable

hue_var_label instance-attribute #

hue_var_label: str

Label for the hue variable in the legend

lines instance-attribute #

Lines to plot

palette instance-attribute #

palette: PALETTE_LIKE[Any]

Palette used for plotting different values of the hue variable

plumes instance-attribute #

Lines to plot

quantile_var_label instance-attribute #

quantile_var_label: str

Label for the quantile variable in the legend

style_var_label instance-attribute #

style_var_label: str | None

Label for the style variable in the legend (if not None)

x_label instance-attribute #

x_label: str | None

Label to apply to the x-axis (if None, no label is applied)

y_label instance-attribute #

y_label: str | None

Label to apply to the y-axis (if None, no label is applied)

from_df classmethod #

from_df(
    df: DataFrame,
    *,
    quantiles_plumes: QUANTILES_PLUMES_LIKE = (
        (0.5, 0.7),
        ((0.05, 0.95), 0.2),
    ),
    quantile_var: str = "quantile",
    quantile_var_label: str | None = None,
    quantile_legend_round: int = 2,
    hue_var: str = "scenario",
    hue_var_label: str | None = None,
    palette: PALETTE_LIKE[Any] | None = None,
    warn_on_palette_value_missing: bool = True,
    style_var: str | None = "variable",
    style_var_label: str | None = None,
    dashes: dict[Any, str | tuple[float, tuple[float, ...]]]
    | None = None,
    warn_on_dashes_value_missing: bool = True,
    linewidth: float = 3.0,
    unit_var: str | None = "unit",
    unit_aware: bool | UnitRegistry = False,
    time_units: str | None = None,
    x_label: str | None = "time",
    y_label: str | bool | None = True,
    warn_infer_y_label_with_multi_unit: bool = True,
    observed: bool = True,
) -> PlumePlotter

Initialise from a pd.DataFrame

Parameters:

Name Type Description Default
df DataFrame

pd.DataFrame from which to initialise

required
quantiles_plumes QUANTILES_PLUMES_LIKE

Quantiles to plot in each plume.

If the first element of each tuple is a tuple, a SinglePlumePlotter object will be created. Otherwise, if the first element is a plain float, a SingleLinePlotter object will be created.

((0.5, 0.7), ((0.05, 0.95), 0.2))
quantile_var str

Variable/column in the multi-index which stores information about the quantile that each timeseries represents.

'quantile'
quantile_var_label str | None

Label to use as the header for the quantile section in the legend

None
quantile_legend_round int

Rounding to apply to quantile values when creating the legend

2
hue_var str

Variable to use for grouping data into different colour groups

'scenario'
hue_var_label str | None

Label to use as the header for the hue/colour section in the legend

None
palette PALETTE_LIKE[Any] | None

Colour to use for the different groups in the data.

If any groups are not included in palette, they are auto-filled.

None
warn_on_palette_value_missing bool

Should a warning be emitted if there are values missing from palette?

True
style_var str | None

Variable to use for grouping data into different (line)style groups

'variable'
style_var_label str | None

Label to use as the header for the style section in the legend

None
dashes dict[Any, str | tuple[float, tuple[float, ...]]] | None

Dash/linestyle to use for the different groups in the data.

If any groups are not included in dashes, they are auto-filled.

None
warn_on_dashes_value_missing bool

Should a warning be emitted if there are values missing from dashes?

True
linewidth float

Width to use for plotting lines.

3.0
unit_var str | None

Variable/column in the multi-index which stores information about the unit of each timeseries.

'unit'
unit_aware bool | UnitRegistry

Should the values be extracted in a unit-aware way?

If True, we use the default application registry (retrieved with pint.get_application_registry). Otherwise, a pint.UnitRegistry can be supplied and will be used.

False
time_units str | None

Units of the time axis of the data.

These are required if unit_aware is not False.

None
x_label str | None

Label to apply to the x-axis.

If None, no label will be applied.

'time'
y_label str | bool | None

Label to apply to the y-axis.

If True, we will try and infer the y-label based on the data's units.

If None, no label will be applied.

True
warn_infer_y_label_with_multi_unit bool

Should a warning be raised if we try to infer the y-unit but the data has more than one unit?

True
observed bool True

Returns:

Type Description
PlumePlotter

Initialised instance

Source code in src/pandas_openscm/plotting.py
@classmethod
def from_df(  # noqa: PLR0912, PLR0913, PLR0915 # object creation code is the worst
    cls,
    df: pd.DataFrame,
    *,
    quantiles_plumes: QUANTILES_PLUMES_LIKE = (
        (0.5, 0.7),
        ((0.05, 0.95), 0.2),
    ),
    quantile_var: str = "quantile",
    quantile_var_label: str | None = None,
    quantile_legend_round: int = 2,
    hue_var: str = "scenario",
    hue_var_label: str | None = None,
    palette: PALETTE_LIKE[Any] | None = None,
    warn_on_palette_value_missing: bool = True,
    style_var: str | None = "variable",
    style_var_label: str | None = None,
    dashes: dict[Any, str | tuple[float, tuple[float, ...]]] | None = None,
    warn_on_dashes_value_missing: bool = True,
    linewidth: float = 3.0,
    unit_var: str | None = "unit",
    unit_aware: bool | pint.UnitRegistry = False,
    time_units: str | None = None,
    x_label: str | None = "time",
    y_label: str | bool | None = True,
    warn_infer_y_label_with_multi_unit: bool = True,
    observed: bool = True,
) -> PlumePlotter:
    """
    Initialise from a [pd.DataFrame][pandas.DataFrame]

    Parameters
    ----------
    df
        [pd.DataFrame][pandas.DataFrame] from which to initialise

    quantiles_plumes
        Quantiles to plot in each plume.

        If the first element of each tuple is a tuple,
        a [SinglePlumePlotter][(m).] object will be created.
        Otherwise, if the first element is a plain float,
        a [SingleLinePlotter][(m).] object will be created.

    quantile_var
        Variable/column in the multi-index which stores information
        about the quantile that each timeseries represents.

    quantile_var_label
        Label to use as the header for the quantile section in the legend

    quantile_legend_round
        Rounding to apply to quantile values when creating the legend

    hue_var
        Variable to use for grouping data into different colour groups

    hue_var_label
        Label to use as the header for the hue/colour section in the legend

    palette
        Colour to use for the different groups in the data.

        If any groups are not included in `palette`,
        they are auto-filled.

    warn_on_palette_value_missing
        Should a warning be emitted if there are values missing from `palette`?

    style_var
        Variable to use for grouping data into different (line)style groups

    style_var_label
        Label to use as the header for the style section in the legend

    dashes
        Dash/linestyle to use for the different groups in the data.

        If any groups are not included in `dashes`,
        they are auto-filled.

    warn_on_dashes_value_missing
        Should a warning be emitted if there are values missing from `dashes`?

    linewidth
        Width to use for plotting lines.

    unit_var
        Variable/column in the multi-index which stores information
        about the unit of each timeseries.

    unit_aware
        Should the values be extracted in a unit-aware way?

        If `True`, we use the default application registry
        (retrieved with [pint.get_application_registry][]).
        Otherwise, a [pint.UnitRegistry][] can be supplied and will be used.

    time_units
        Units of the time axis of the data.

        These are required if `unit_aware` is not `False`.

    x_label
        Label to apply to the x-axis.

        If `None`, no label will be applied.

    y_label
        Label to apply to the y-axis.

        If `True`, we will try and infer the y-label based on the data's units.

        If `None`, no label will be applied.

    warn_infer_y_label_with_multi_unit
        Should a warning be raised if we try to infer the y-unit
        but the data has more than one unit?

    observed
        Passed to [pd.DataFrame.groupby][pandas.DataFrame.groupby].

    Returns
    -------
    :
         Initialised instance
    """
    if hue_var_label is None:
        hue_var_label = hue_var.capitalize()

    if style_var is not None and style_var_label is None:
        style_var_label = style_var.capitalize()

    if quantile_var_label is None:
        quantile_var_label = quantile_var.capitalize()

    infer_y_label = (
        (not unit_aware)
        and isinstance(y_label, bool)
        and y_label
        and unit_var is not None
    )

    palette_complete = fill_out_palette(
        df.index.unique(hue_var),
        palette_user_supplied=palette,
        warn_on_value_missing=warn_on_palette_value_missing,
    )

    if style_var is not None:
        group_cols = [hue_var, style_var]
        dashes_complete = fill_out_dashes(
            df.index.unique(style_var),
            dashes_user_supplied=dashes,
            warn_on_value_missing=warn_on_dashes_value_missing,
        )

    else:
        group_cols = [hue_var]
        dashes_complete = None

    lines: list[SingleLinePlotter] = []
    plumes: list[SinglePlumePlotter] = []
    values_units: list[str] = []
    for info, gdf in df.groupby(group_cols, observed=observed):
        info_d = {k: v for k, v in zip(group_cols, info)}

        colour = palette_complete[info_d[hue_var]]

        gpdf = partial(get_pdf_from_pre_calculated, gdf, quantile_col=quantile_var)

        def warn_about_missing_quantile(exc: Exception) -> None:
            warnings.warn(
                f"Quantiles missing for {info_d}. Original exception: {exc}"
            )

        for q, alpha in quantiles_plumes:
            if not isinstance(q, tuple):
                quantile = float(q)
                if style_var is not None:
                    if dashes_complete is None:  # pragma: no cover
                        # should be impossible to hit this
                        raise AssertionError
                    linestyle = dashes_complete[info_d[style_var]]
                else:
                    linestyle = "-"

                try:
                    line_quantiles = (quantile,)
                    pdf = gpdf(quantiles=line_quantiles)
                except MissingQuantileError as exc:
                    warn_about_missing_quantile(exc=exc)
                    continue

                x_vals, y_vals = get_values_line(
                    pdf,
                    unit_aware=unit_aware,
                    unit_var=unit_var,
                    time_units=time_units,
                )
                line_plotter = SingleLinePlotter(
                    x_vals=x_vals,
                    y_vals=y_vals,
                    quantile=quantile,
                    linewidth=linewidth,
                    linestyle=linestyle,
                    color=colour,
                    alpha=alpha,
                )
                lines.append(line_plotter)

            else:
                plume_quantiles = (float(q[0]), float(q[1]))
                try:
                    pdf = gpdf(quantiles=plume_quantiles)
                except MissingQuantileError as exc:
                    warn_about_missing_quantile(exc=exc)
                    continue

                x_vals, y_vals_lower, y_vals_upper = get_values_plume(
                    pdf,
                    quantiles=plume_quantiles,
                    quantile_var=quantile_var,
                    unit_aware=unit_aware,
                    unit_var=unit_var,
                    time_units=time_units,
                )
                plume_plotter = SinglePlumePlotter(
                    x_vals=x_vals,
                    y_vals_lower=y_vals_lower,
                    y_vals_upper=y_vals_upper,
                    quantiles=plume_quantiles,
                    color=colour,
                    alpha=alpha,
                )
                plumes.append(plume_plotter)

            if (
                infer_y_label
                and unit_var is not None
                and unit_var in pdf.index.names
            ):
                values_units.extend(pdf.index.unique(unit_var))

    if unit_aware and isinstance(y_label, bool) and y_label:
        # Let unit-aware plotting do its thing
        y_label = None

    elif unit_var is None:
        y_label = None

    elif infer_y_label:
        if unit_var not in df.index.names:
            warnings.warn(
                "Not auto-setting the y_label "
                f"because {unit_var=} is not in {df.index.names=}"
            )
            y_label = None

        else:
            # Try to infer the y-label
            units_s = set(values_units)
            if len(units_s) == 1:
                y_label = values_units[0]
            else:
                # More than one unit plotted, don't infer a y-label
                if warn_infer_y_label_with_multi_unit:
                    warnings.warn(
                        "Not auto-setting the y_label "
                        "because the plotted data has more than one unit: "
                        f"data units {units_s}"
                    )

                y_label = None

    if isinstance(y_label, bool):
        msg = "y_label should have been converted before getting here"
        raise TypeError(msg)

    res = PlumePlotter(
        lines=lines,
        plumes=plumes,
        hue_var_label=hue_var_label,
        style_var_label=style_var_label,
        quantile_var_label=quantile_var_label,
        palette=palette_complete,
        dashes=dashes_complete,
        x_label=x_label,
        y_label=y_label,
    )

    return res

generate_legend_handles #

generate_legend_handles(
    quantile_legend_round: int = 2,
) -> list[Artist]

Generate handles for the legend

Parameters:

Name Type Description Default
quantile_legend_round int

Rounding to apply to the quantiles when creating the label

2

Returns:

Type Description
list[Artist]

Generated handles for the legend

Source code in src/pandas_openscm/plotting.py
def generate_legend_handles(
    self, quantile_legend_round: int = 2
) -> list[matplotlib.artist.Artist]:
    """
    Generate handles for the legend

    Parameters
    ----------
    quantile_legend_round
        Rounding to apply to the quantiles when creating the label

    Returns
    -------
    :
        Generated handles for the legend
    """
    try:
        import matplotlib.lines as mlines  # noqa: PLC0415
        import matplotlib.patches as mpatches  # noqa: PLC0415
    except ImportError as exc:
        raise MissingOptionalDependencyError(
            "generate_legend_handles", requirement="matplotlib"
        ) from exc

    generated_quantile_items: list[
        Union[
            tuple[float, float, str],
            tuple[tuple[float, float], float, str],
        ]
    ] = []
    quantile_items: list[matplotlib.artist.Artist] = []
    for line in self.lines:
        label = line.get_label(quantile_legend_round=quantile_legend_round)
        pid_line = (line.quantile, line.alpha, label)
        if pid_line in generated_quantile_items:
            continue

        quantile_items.append(
            mlines.Line2D([0], [0], color="k", alpha=line.alpha, label=label)
        )
        generated_quantile_items.append(pid_line)

    for plume in self.plumes:
        label = plume.get_label(quantile_legend_round=quantile_legend_round)
        pid_plume = (plume.quantiles, plume.alpha, label)
        if pid_plume in generated_quantile_items:
            continue

        quantile_items.append(
            mpatches.Patch(color="k", alpha=plume.alpha, label=label)
        )
        generated_quantile_items.append(pid_plume)

    hue_items = [
        mlines.Line2D([0], [0], color=colour, label=hue_value)
        for hue_value, colour in self.palette.items()
    ]

    legend_items = [
        mpatches.Patch(alpha=0, label=self.quantile_var_label),
        *quantile_items,
        mpatches.Patch(alpha=0, label=self.hue_var_label),
        *hue_items,
    ]
    if self.dashes is not None and self.lines:
        style_items = [
            mlines.Line2D(
                [0],
                [0],
                linestyle=linestyle,
                label=style_value,
                color="gray",
            )
            for style_value, linestyle in self.dashes.items()
        ]
        legend_items.append(mpatches.Patch(alpha=0, label=self.style_var_label))
        legend_items.extend(style_items)

    return legend_items

plot #

plot(
    ax: Axes | None = None,
    *,
    create_legend: Callable[
        [Axes, list[Artist]], None
    ] = create_legend_default,
    quantile_legend_round: int = 2,
) -> Axes

Plot

Parameters:

Name Type Description Default
ax Axes | None

Axes onto which to plot

None
create_legend Callable[[Axes, list[Artist]], None]

Function to use to create the legend.

This allows the user to have full control over the creation of the legend.

create_legend_default
quantile_legend_round int

Rounding to apply to quantile values when creating the legend

2

Returns:

Type Description
Axes

Axes on which the data was plotted

Source code in src/pandas_openscm/plotting.py
def plot(
    self,
    ax: matplotlib.axes.Axes | None = None,
    *,
    create_legend: Callable[
        [matplotlib.axes.Axes, list[matplotlib.artist.Artist]], None
    ] = create_legend_default,
    quantile_legend_round: int = 2,
) -> matplotlib.axes.Axes:
    """
    Plot

    Parameters
    ----------
    ax
        Axes onto which to plot

    create_legend
        Function to use to create the legend.

        This allows the user to have full control over the creation of the legend.

    quantile_legend_round
        Rounding to apply to quantile values when creating the legend

    Returns
    -------
    :
        Axes on which the data was plotted
    """
    if ax is None:
        try:
            import matplotlib.pyplot as plt  # noqa: PLC0415
        except ImportError as exc:
            raise MissingOptionalDependencyError(  # noqa: TRY003
                "plot(ax=None, ...)", requirement="matplotlib"
            ) from exc

        _, ax = plt.subplots()

    for plume in self.plumes:
        plume.plot(ax=ax)

    for line in self.lines:
        line.plot(ax=ax)

    create_legend(
        ax,
        self.generate_legend_handles(quantile_legend_round=quantile_legend_round),
    )

    if self.x_label is not None:
        ax.set_xlabel(self.x_label)

    if self.y_label is not None:
        ax.set_ylabel(self.y_label)

    return ax

SingleLinePlotter #

Object which is able to plot single lines

Methods:

Name Description
get_label

Get the label for the line

plot

Plot

Attributes:

Name Type Description
alpha float

Alpha to use when plotting the line

color COLOUR_VALUE_LIKE

Colour to use when plotting the line

linestyle DASH_VALUE_LIKE

Style to use when plotting the line

linewidth float

Linewidth to use when plotting the line

pkwargs dict[str, Any] | None

Other arguments to pass to matplotlib.axes.Axes.plot when plotting

quantile float

Quantile that this line represents

x_vals NP_ARRAY_OF_FLOAT_OR_INT | PINT_NUMPY_ARRAY

x-values to plot

y_vals NP_ARRAY_OF_FLOAT_OR_INT | PINT_NUMPY_ARRAY

y-values to plot

Source code in src/pandas_openscm/plotting.py
@define
class SingleLinePlotter:
    """Object which is able to plot single lines"""

    x_vals: NP_ARRAY_OF_FLOAT_OR_INT | PINT_NUMPY_ARRAY
    """x-values to plot"""

    y_vals: NP_ARRAY_OF_FLOAT_OR_INT | PINT_NUMPY_ARRAY = field(
        validator=[same_shape_as_x_vals]
    )
    """y-values to plot"""

    quantile: float
    """Quantile that this line represents"""

    linewidth: float
    """Linewidth to use when plotting the line"""

    linestyle: DASH_VALUE_LIKE
    """Style to use when plotting the line"""

    color: COLOUR_VALUE_LIKE
    """Colour to use when plotting the line"""

    alpha: float
    """Alpha to use when plotting the line"""

    pkwargs: dict[str, Any] | None = None
    """Other arguments to pass to [matplotlib.axes.Axes.plot][] when plotting"""

    def get_label(self, quantile_legend_round: int = 2) -> str:
        """
        Get the label for the line

        Parameters
        ----------
        quantile_legend_round
            Rounding to apply to the quantile when creating the label

        Returns
        -------
        :
            Label for the line
        """
        label = str(np.round(self.quantile, quantile_legend_round))

        return label

    def plot(self, ax: matplotlib.axes.Axes, quantile_legend_round: int = 2) -> None:
        """
        Plot

        Parameters
        ----------
        ax
            Axes on which to plot

        quantile_legend_round
            Rounding to apply to the quantile when creating the label
        """
        pkwargs = self.pkwargs if self.pkwargs is not None else {}

        ax.plot(
            self.x_vals,
            self.y_vals,
            label=self.get_label(quantile_legend_round=quantile_legend_round),
            linewidth=self.linewidth,
            linestyle=self.linestyle,
            color=self.color,
            alpha=self.alpha,
            **pkwargs,
        )

alpha instance-attribute #

alpha: float

Alpha to use when plotting the line

color instance-attribute #

Colour to use when plotting the line

linestyle instance-attribute #

linestyle: DASH_VALUE_LIKE

Style to use when plotting the line

linewidth instance-attribute #

linewidth: float

Linewidth to use when plotting the line

pkwargs class-attribute instance-attribute #

pkwargs: dict[str, Any] | None = None

Other arguments to pass to matplotlib.axes.Axes.plot when plotting

quantile instance-attribute #

quantile: float

Quantile that this line represents

x_vals instance-attribute #

x-values to plot

y_vals class-attribute instance-attribute #

y-values to plot

get_label #

get_label(quantile_legend_round: int = 2) -> str

Get the label for the line

Parameters:

Name Type Description Default
quantile_legend_round int

Rounding to apply to the quantile when creating the label

2

Returns:

Type Description
str

Label for the line

Source code in src/pandas_openscm/plotting.py
def get_label(self, quantile_legend_round: int = 2) -> str:
    """
    Get the label for the line

    Parameters
    ----------
    quantile_legend_round
        Rounding to apply to the quantile when creating the label

    Returns
    -------
    :
        Label for the line
    """
    label = str(np.round(self.quantile, quantile_legend_round))

    return label

plot #

plot(ax: Axes, quantile_legend_round: int = 2) -> None

Plot

Parameters:

Name Type Description Default
ax Axes

Axes on which to plot

required
quantile_legend_round int

Rounding to apply to the quantile when creating the label

2
Source code in src/pandas_openscm/plotting.py
def plot(self, ax: matplotlib.axes.Axes, quantile_legend_round: int = 2) -> None:
    """
    Plot

    Parameters
    ----------
    ax
        Axes on which to plot

    quantile_legend_round
        Rounding to apply to the quantile when creating the label
    """
    pkwargs = self.pkwargs if self.pkwargs is not None else {}

    ax.plot(
        self.x_vals,
        self.y_vals,
        label=self.get_label(quantile_legend_round=quantile_legend_round),
        linewidth=self.linewidth,
        linestyle=self.linestyle,
        color=self.color,
        alpha=self.alpha,
        **pkwargs,
    )

SinglePlumePlotter #

Object which is able to plot single plumes

Methods:

Name Description
get_label

Get the label for the plume

plot

Plot

Attributes:

Name Type Description
alpha float

Alpha to use when plotting the plume

color COLOUR_VALUE_LIKE

Colour to use when plotting the plume

pkwargs dict[str, Any] | None

Other arguments to pass to matplotlib.axes.Axes.fill_between when plotting

quantiles tuple[float, float]

Quantiles that this plume represents

x_vals NP_ARRAY_OF_FLOAT_OR_INT | PINT_NUMPY_ARRAY

x-values to plot

y_vals_lower NP_ARRAY_OF_FLOAT_OR_INT | PINT_NUMPY_ARRAY

y-values to plot as the lower bound of the plume

y_vals_upper NP_ARRAY_OF_FLOAT_OR_INT | PINT_NUMPY_ARRAY

y-values to plot as the upper bound of the plume

Source code in src/pandas_openscm/plotting.py
@define
class SinglePlumePlotter:
    """Object which is able to plot single plumes"""

    x_vals: NP_ARRAY_OF_FLOAT_OR_INT | PINT_NUMPY_ARRAY
    """x-values to plot"""

    y_vals_lower: NP_ARRAY_OF_FLOAT_OR_INT | PINT_NUMPY_ARRAY = field(
        validator=[same_shape_as_x_vals]
    )
    """y-values to plot as the lower bound of the plume"""

    y_vals_upper: NP_ARRAY_OF_FLOAT_OR_INT | PINT_NUMPY_ARRAY = field(
        validator=[same_shape_as_x_vals]
    )
    """y-values to plot as the upper bound of the plume"""

    quantiles: tuple[float, float]
    """Quantiles that this plume represents"""

    color: COLOUR_VALUE_LIKE
    """Colour to use when plotting the plume"""

    alpha: float
    """Alpha to use when plotting the plume"""

    pkwargs: dict[str, Any] | None = None
    """Other arguments to pass to [matplotlib.axes.Axes.fill_between][] when plotting"""

    def get_label(self, quantile_legend_round: int = 2) -> str:
        """
        Get the label for the plume

        Parameters
        ----------
        quantile_legend_round
            Rounding to apply to the quantiles when creating the label

        Returns
        -------
        :
            Label for the plume
        """
        label = " - ".join(
            [str(np.round(qv, quantile_legend_round)) for qv in self.quantiles]
        )

        return label

    def plot(self, ax: matplotlib.axes.Axes, quantile_legend_round: int = 2) -> None:
        """
        Plot

        Parameters
        ----------
        ax
            Axes on which to plot

        quantile_legend_round
            Rounding to apply to the quantiles when creating the label
        """
        pkwargs = self.pkwargs if self.pkwargs is not None else {}

        ax.fill_between(
            self.x_vals,
            self.y_vals_lower,
            self.y_vals_upper,
            label=self.get_label(quantile_legend_round=quantile_legend_round),
            facecolor=self.color,
            alpha=self.alpha,
            **pkwargs,
        )

alpha instance-attribute #

alpha: float

Alpha to use when plotting the plume

color instance-attribute #

Colour to use when plotting the plume

pkwargs class-attribute instance-attribute #

pkwargs: dict[str, Any] | None = None

Other arguments to pass to matplotlib.axes.Axes.fill_between when plotting

quantiles instance-attribute #

quantiles: tuple[float, float]

Quantiles that this plume represents

x_vals instance-attribute #

x-values to plot

y_vals_lower class-attribute instance-attribute #

y-values to plot as the lower bound of the plume

y_vals_upper class-attribute instance-attribute #

y-values to plot as the upper bound of the plume

get_label #

get_label(quantile_legend_round: int = 2) -> str

Get the label for the plume

Parameters:

Name Type Description Default
quantile_legend_round int

Rounding to apply to the quantiles when creating the label

2

Returns:

Type Description
str

Label for the plume

Source code in src/pandas_openscm/plotting.py
def get_label(self, quantile_legend_round: int = 2) -> str:
    """
    Get the label for the plume

    Parameters
    ----------
    quantile_legend_round
        Rounding to apply to the quantiles when creating the label

    Returns
    -------
    :
        Label for the plume
    """
    label = " - ".join(
        [str(np.round(qv, quantile_legend_round)) for qv in self.quantiles]
    )

    return label

plot #

plot(ax: Axes, quantile_legend_round: int = 2) -> None

Plot

Parameters:

Name Type Description Default
ax Axes

Axes on which to plot

required
quantile_legend_round int

Rounding to apply to the quantiles when creating the label

2
Source code in src/pandas_openscm/plotting.py
def plot(self, ax: matplotlib.axes.Axes, quantile_legend_round: int = 2) -> None:
    """
    Plot

    Parameters
    ----------
    ax
        Axes on which to plot

    quantile_legend_round
        Rounding to apply to the quantiles when creating the label
    """
    pkwargs = self.pkwargs if self.pkwargs is not None else {}

    ax.fill_between(
        self.x_vals,
        self.y_vals_lower,
        self.y_vals_upper,
        label=self.get_label(quantile_legend_round=quantile_legend_round),
        facecolor=self.color,
        alpha=self.alpha,
        **pkwargs,
    )

create_legend_default #

create_legend_default(
    ax: Axes, handles: list[Artist]
) -> None

Create legend, default implementation

Intended to be used with plot_plume_func

Parameters:

Name Type Description Default
ax Axes

Axes on which to create the legend

required
handles list[Artist]

Handles to include in the legend

required
Source code in src/pandas_openscm/plotting.py
def create_legend_default(
    ax: matplotlib.axes.Axes, handles: list[matplotlib.artist.Artist]
) -> None:
    """
    Create legend, default implementation

    Intended to be used with [plot_plume_func][(m).]

    Parameters
    ----------
    ax
        Axes on which to create the legend

    handles
        Handles to include in the legend
    """
    ax.legend(handles=handles, loc="center left", bbox_to_anchor=(1.05, 0.5))

extract_single_unit #

extract_single_unit(df: DataFrame, unit_var: str) -> str

Extract the unit of the data, expecting there to only be one unit

Parameters:

Name Type Description Default
df DataFrame

pd.DataFrame from which to get the unit

required
unit_var str

Variable/column in the multi-index which holds unit information

required

Returns:

Type Description
str

Unit of the data

Raises:

Type Description
AssertionError

The data has more than one unit

Source code in src/pandas_openscm/plotting.py
def extract_single_unit(df: pd.DataFrame, unit_var: str) -> str:
    """
    Extract the unit of the data, expecting there to only be one unit

    Parameters
    ----------
    df
        [pd.DataFrame][pandas.DataFrame] from which to get the unit

    unit_var
        Variable/column in the multi-index which holds unit information

    Returns
    -------
    :
        Unit of the data

    Raises
    ------
    AssertionError
        The data has more than one unit
    """
    units = df.index.unique(unit_var).tolist()
    if len(units) != 1:
        raise AssertionError(units)

    return cast(str, units[0])

fill_out_dashes #

fill_out_dashes(
    style_values: Iterable[T],
    dashes_user_supplied: dict[T, DASH_VALUE_LIKE] | None,
    warn_on_value_missing: bool,
) -> dict[T, DASH_VALUE_LIKE]

Fill out dashes

Parameters:

Name Type Description Default
style_values Iterable[T]

Values which require a value in the output dashes

required
dashes_user_supplied dict[T, DASH_VALUE_LIKE] | None

User-supplied dashes

required
warn_on_value_missing bool

Should a warning be emitted if dashes_user_supplied is not None but there are values missing from dashes_user_supplied?

required

Returns:

Type Description
dict[T, DASH_VALUE_LIKE]

Dashes with values for all style_values

Warns:

Type Description
UserWarning

warn_on_value_missing is True, dashes_user_supplied is not None and there are values in style_values which are not in dashes_user_supplied.

Source code in src/pandas_openscm/plotting.py
def fill_out_dashes(
    style_values: Iterable[T],
    dashes_user_supplied: dict[T, DASH_VALUE_LIKE] | None,
    warn_on_value_missing: bool,
) -> dict[T, DASH_VALUE_LIKE]:
    """
    Fill out dashes

    Parameters
    ----------
    style_values
        Values which require a value in the output dashes

    dashes_user_supplied
        User-supplied dashes

    warn_on_value_missing
        Should a warning be emitted if `dashes_user_supplied` is not `None`
        but there are values missing from `dashes_user_supplied`?

    Returns
    -------
    :
        Dashes with values for all `style_values`

    Warns
    -----
    UserWarning
        `warn_on_value_missing` is `True`,
        `dashes_user_supplied` is not `None`
        and there are values in `style_values` which are not in `dashes_user_supplied`.
    """
    if dashes_user_supplied is None:
        # Make it all ourselves.
        # Don't warn as the user didn't set any values
        # so it is clear they want us to fill in everything.
        dash_cycler = get_default_dash_cycler()
        dashes_out = {v: next(dash_cycler) for v in style_values}

        return dashes_out

    # User-supplied palette
    missing_from_user_supplied = [
        v for v in style_values if v not in dashes_user_supplied
    ]
    if not missing_from_user_supplied:
        # Just return the values we need
        return {v: dashes_user_supplied[v] for v in style_values}

    if warn_on_value_missing:
        msg = (
            f"Some style values are not in the user-supplied dashes, "
            "they will be filled from the default dash cycler instead. "
            f"{missing_from_user_supplied=} {dashes_user_supplied=}"
        )
        warnings.warn(msg)

    dashes_out = {}
    dash_cycler = get_default_dash_cycler()
    for v in style_values:
        dashes_out[v] = (
            dashes_user_supplied[v] if v in dashes_user_supplied else next(dash_cycler)
        )

    return dashes_out

fill_out_palette #

fill_out_palette(
    hue_values: Iterable[T],
    palette_user_supplied: PALETTE_LIKE[T] | None,
    warn_on_value_missing: bool,
) -> PALETTE_LIKE[T]

Fill out a palette

Parameters:

Name Type Description Default
hue_values Iterable[T]

Values which require a value in the output palette

required
palette_user_supplied PALETTE_LIKE[T] | None

User-supplied palette

required
warn_on_value_missing bool

Should a warning be emitted if palette_user_supplied is not None but there are values missing from palette_user_supplied?

required

Returns:

Type Description
PALETTE_LIKE[T]

Palette with values for all hue_values

Warns:

Type Description
UserWarning

warn_on_value_missing is True, palette_user_supplied is not None and there are values in hue_values which are not in palette_user_supplied.

Source code in src/pandas_openscm/plotting.py
def fill_out_palette(
    hue_values: Iterable[T],
    palette_user_supplied: PALETTE_LIKE[T] | None,
    warn_on_value_missing: bool,
) -> PALETTE_LIKE[T]:
    """
    Fill out a palette

    Parameters
    ----------
    hue_values
        Values which require a value in the output palette

    palette_user_supplied
        User-supplied palette

    warn_on_value_missing
        Should a warning be emitted if `palette_user_supplied` is not `None`
        but there are values missing from `palette_user_supplied`?

    Returns
    -------
    :
        Palette with values for all `hue_values`

    Warns
    -----
    UserWarning
        `warn_on_value_missing` is `True`,
        `palette_user_supplied` is not `None`
        and there are values in `hue_values` which are not in `palette_user_supplied`.
    """
    if palette_user_supplied is None:
        # Make it all ourselves.
        # Don't warn as the user didn't set any values
        # so it is clear they want us to fill in everything.
        colour_cycler = get_default_colour_cycler()
        palette_out: PALETTE_LIKE[T] = {v: next(colour_cycler) for v in hue_values}

        return palette_out

    # User-supplied palette
    missing_from_user_supplied = [
        v for v in hue_values if v not in palette_user_supplied
    ]
    if not missing_from_user_supplied:
        # Just return the values we need
        return {v: palette_user_supplied[v] for v in hue_values}

    if warn_on_value_missing:
        msg = (
            f"Some hue values are not in the user-supplied palette, "
            "they will be filled from the default colour cycler instead. "
            f"{missing_from_user_supplied=} {palette_user_supplied=}"
        )
        warnings.warn(msg)

    colour_cycler = get_default_colour_cycler()
    palette_out = {
        k: (
            palette_user_supplied[k]
            if k in palette_user_supplied
            else next(colour_cycler)
        )
        for k in hue_values
    }

    return palette_out

get_default_colour_cycler #

get_default_colour_cycler() -> Iterator[COLOUR_VALUE_LIKE]

Get the default colour cycler

Returns:

Type Description
Iterator[COLOUR_VALUE_LIKE]

Default colour cycler

Raises:

Type Description
MissingOptionalDependencyError

matplotlib is not installed

Source code in src/pandas_openscm/plotting.py
def get_default_colour_cycler() -> Iterator[COLOUR_VALUE_LIKE]:
    """
    Get the default colour cycler

    Returns
    -------
    :
        Default colour cycler

    Raises
    ------
    MissingOptionalDependencyError
        [matplotlib][] is not installed
    """
    try:
        import matplotlib.pyplot as plt  # noqa: PLC0415
    except ImportError as exc:
        raise MissingOptionalDependencyError(
            "get_default_colour_cycler", requirement="matplotlib"
        ) from exc

    colour_cycler = cycle(plt.rcParams["axes.prop_cycle"].by_key()["color"])

    return colour_cycler

get_default_dash_cycler #

get_default_dash_cycler() -> Iterator[DASH_VALUE_LIKE]

Get the default dash cycler

Returns:

Type Description
Iterator[DASH_VALUE_LIKE]

Default dash cycler

Source code in src/pandas_openscm/plotting.py
def get_default_dash_cycler() -> Iterator[DASH_VALUE_LIKE]:
    """
    Get the default dash cycler

    Returns
    -------
    :
        Default dash cycler
    """
    dash_cycler = cycle(["-", "--", "-.", ":"])

    return dash_cycler

get_pdf_from_pre_calculated #

get_pdf_from_pre_calculated(
    in_df: DataFrame,
    *,
    quantiles: Iterable[float],
    quantile_col: str,
) -> DataFrame

Get a pd.DataFrame for plotting from pre-calculated quantiles

Parameters:

Name Type Description Default
in_df DataFrame

Input pd.DataFrame

required
quantiles Iterable[float]

Quantiles to grab

required
quantile_col str

Name of the index column in which quantile information is stored

required

Returns:

Type Description
DataFrame

pd.DataFrame to use for plotting.

Raises:

Type Description
MissingQuantileError

One of the quantiles in quantiles is not available in in_df.

Source code in src/pandas_openscm/plotting.py
def get_pdf_from_pre_calculated(
    in_df: pd.DataFrame,
    *,
    quantiles: Iterable[float],
    quantile_col: str,
) -> pd.DataFrame:
    """
    Get a [pd.DataFrame][pandas.DataFrame] for plotting from pre-calculated quantiles

    Parameters
    ----------
    in_df
        Input [pd.DataFrame][pandas.DataFrame]

    quantiles
        Quantiles to grab

    quantile_col
        Name of the index column in which quantile information is stored

    Returns
    -------
    :
        [pd.DataFrame][pandas.DataFrame] to use for plotting.

    Raises
    ------
    MissingQuantileError
        One of the quantiles in `quantiles` is not available in `in_df`.
    """
    missing_quantiles = []
    available_quantiles = in_df.index.unique(quantile_col).tolist()
    for qt in quantiles:
        if qt not in available_quantiles:
            missing_quantiles.append(qt)

    if missing_quantiles:
        raise MissingQuantileError(available_quantiles, missing_quantiles)

    # otherwise, have what we need
    pdf = in_df.loc[in_df.index.get_level_values(quantile_col).isin(quantiles)]

    return pdf

get_quantiles #

get_quantiles(
    quantiles_plumes: QUANTILES_PLUMES_LIKE,
) -> NDArray[floating[Any]]

Get just the quantiles from a QUANTILES_PLUMES_LIKE

Parameters:

Name Type Description Default
quantiles_plumes QUANTILES_PLUMES_LIKE

Quantiles-plumes definition

required

Returns:

Type Description
NDArray[floating[Any]]

Quantiles to be used in plotting

Source code in src/pandas_openscm/plotting.py
def get_quantiles(
    quantiles_plumes: QUANTILES_PLUMES_LIKE,
) -> np.typing.NDArray[np.floating[Any]]:
    """
    Get just the quantiles from a [QUANTILES_PLUMES_LIKE][(m).]

    Parameters
    ----------
    quantiles_plumes
        Quantiles-plumes definition

    Returns
    -------
    :
        Quantiles to be used in plotting
    """
    quantiles_l: list[float] = []
    for quantile_plot_def in quantiles_plumes:
        q_def = quantile_plot_def[0]
        if isinstance(q_def, tuple):
            for q in q_def:
                quantiles_l.append(q)
        else:
            quantiles_l.append(q_def)

    return cast(np.typing.NDArray[np.floating[Any]], np.unique(np.asarray(quantiles_l)))

get_values_line #

get_values_line(
    pdf: DataFrame,
    *,
    unit_aware: Literal[False],
    unit_var: str | None,
    time_units: str | None,
) -> tuple[
    NP_ARRAY_OF_FLOAT_OR_INT, NP_ARRAY_OF_FLOAT_OR_INT
]
get_values_line(
    pdf: DataFrame,
    *,
    unit_aware: Literal[True] | UnitRegistry,
    unit_var: str | None,
    time_units: str | None,
) -> tuple[PINT_NUMPY_ARRAY, PINT_NUMPY_ARRAY]
get_values_line(
    pdf: DataFrame,
    *,
    unit_aware: bool | UnitRegistry,
    unit_var: str | None,
    time_units: str | None,
) -> (
    tuple[
        NP_ARRAY_OF_FLOAT_OR_INT, NP_ARRAY_OF_FLOAT_OR_INT
    ]
    | tuple[PINT_NUMPY_ARRAY, PINT_NUMPY_ARRAY]
)
get_values_line(
    pdf: DataFrame,
    *,
    unit_aware: bool | UnitRegistry,
    unit_var: str | None,
    time_units: str | None,
) -> (
    tuple[
        NP_ARRAY_OF_FLOAT_OR_INT, NP_ARRAY_OF_FLOAT_OR_INT
    ]
    | tuple[PINT_NUMPY_ARRAY, PINT_NUMPY_ARRAY]
)

Get values for plotting a line

Parameters:

Name Type Description Default
pdf DataFrame

pd.DataFrame from which to get the values

required
unit_aware bool | UnitRegistry

Should the values be unit-aware?

If True, we use the default application registry (retrieved with pint.get_application_registry). Otherwise, a pint.UnitRegistry can be supplied and will be used.

required
unit_var str | None

Variable/column in the multi-index which stores information about the unit of each timeseries.

required
time_units str | None

Units of the time axis.

required

Returns:

Name Type Description
x_values tuple[NP_ARRAY_OF_FLOAT_OR_INT, NP_ARRAY_OF_FLOAT_OR_INT] | tuple[PINT_NUMPY_ARRAY, PINT_NUMPY_ARRAY]

x-values (for a plot)

y_values tuple[NP_ARRAY_OF_FLOAT_OR_INT, NP_ARRAY_OF_FLOAT_OR_INT] | tuple[PINT_NUMPY_ARRAY, PINT_NUMPY_ARRAY]

y-values (for a plot)

Raises:

Type Description
TypeError

unit_aware is not False and unit_var or time_units is None.

MissingOptionalDependencyError

unit_aware is True and pint is not installed.

Source code in src/pandas_openscm/plotting.py
def get_values_line(
    pdf: pd.DataFrame,
    *,
    unit_aware: bool | pint.UnitRegistry,
    unit_var: str | None,
    time_units: str | None,
) -> (
    tuple[NP_ARRAY_OF_FLOAT_OR_INT, NP_ARRAY_OF_FLOAT_OR_INT]
    | tuple[PINT_NUMPY_ARRAY, PINT_NUMPY_ARRAY]
):
    """
    Get values for plotting a line

    Parameters
    ----------
    pdf
        [pd.DataFrame][pandas.DataFrame] from which to get the values

    unit_aware
        Should the values be unit-aware?

        If `True`, we use the default application registry
        (retrieved with [pint.get_application_registry][]).
        Otherwise, a [pint.UnitRegistry][] can be supplied and will be used.

    unit_var
        Variable/column in the multi-index which stores information
        about the unit of each timeseries.

    time_units
        Units of the time axis.

    Returns
    -------
    x_values :
        x-values (for a plot)

    y_values :
        y-values (for a plot)

    Raises
    ------
    TypeError
        `unit_aware` is not `False` and `unit_var` or `time_units` is `None`.

    MissingOptionalDependencyError
        `unit_aware` is `True`
        and [pint](https://pint.readthedocs.io/) is not installed.
    """
    res_no_units = (pdf.columns.values.squeeze(), pdf.values.squeeze())
    if not unit_aware:
        return res_no_units

    if unit_var is None:
        msg = "If `unit_aware` != False, then `unit_var` must not be `None`"
        raise TypeError(msg)

    if time_units is None:
        msg = "If `unit_aware` != False, then `time_units` must not be `None`"
        raise TypeError(msg)

    if isinstance(unit_aware, bool):
        try:
            import pint  # noqa: PLC0415
        except ImportError as exc:
            raise MissingOptionalDependencyError(  # noqa: TRY003
                "get_values_line(..., unit_aware=True, ...)", requirement="pint"
            ) from exc

        ur = pint.get_application_registry()  # type: ignore[no-untyped-call] # pint typing limited

    else:
        ur = unit_aware

    res = (
        res_no_units[0] * ur(time_units),
        res_no_units[1] * ur(extract_single_unit(pdf, unit_var)),
    )

    return res

get_values_plume #

get_values_plume(
    pdf: DataFrame,
    *,
    quantiles: tuple[float, float],
    quantile_var: str,
    unit_aware: Literal[False],
    unit_var: str | None,
    time_units: str | None,
) -> tuple[
    NP_ARRAY_OF_FLOAT_OR_INT,
    NP_ARRAY_OF_FLOAT_OR_INT,
    NP_ARRAY_OF_FLOAT_OR_INT,
]
get_values_plume(
    pdf: DataFrame,
    *,
    quantiles: tuple[float, float],
    quantile_var: str,
    unit_aware: Literal[True] | UnitRegistry,
    unit_var: str | None,
    time_units: str | None,
) -> tuple[
    PINT_NUMPY_ARRAY, PINT_NUMPY_ARRAY, PINT_NUMPY_ARRAY
]
get_values_plume(
    pdf: DataFrame,
    *,
    quantiles: tuple[float, float],
    quantile_var: str,
    unit_aware: bool | UnitRegistry,
    unit_var: str | None,
    time_units: str | None,
) -> (
    tuple[
        NP_ARRAY_OF_FLOAT_OR_INT,
        NP_ARRAY_OF_FLOAT_OR_INT,
        NP_ARRAY_OF_FLOAT_OR_INT,
    ]
    | tuple[
        PINT_NUMPY_ARRAY, PINT_NUMPY_ARRAY, PINT_NUMPY_ARRAY
    ]
)
get_values_plume(
    pdf: DataFrame,
    *,
    quantiles: tuple[float, float],
    quantile_var: str,
    unit_aware: bool | UnitRegistry,
    unit_var: str | None,
    time_units: str | None,
) -> (
    tuple[
        NP_ARRAY_OF_FLOAT_OR_INT,
        NP_ARRAY_OF_FLOAT_OR_INT,
        NP_ARRAY_OF_FLOAT_OR_INT,
    ]
    | tuple[
        PINT_NUMPY_ARRAY, PINT_NUMPY_ARRAY, PINT_NUMPY_ARRAY
    ]
)

Get values for plotting a line

Parameters:

Name Type Description Default
pdf DataFrame

pd.DataFrame from which to get the values

required
quantiles tuple[float, float]

Quantiles to get from pdf

required
quantile_var str

Variable/column in the multi-index which stores information about the quantile that each timeseries represents.

required
unit_aware bool | UnitRegistry

Should the values be unit-aware?

If True, we use the default application registry (retrieved with pint.get_application_registry). Otherwise, a pint.UnitRegistry can be supplied and will be used.

required
unit_var str | None

Variable/column in the multi-index which stores information about the unit of each timeseries.

required
time_units str | None

Units of the time axis.

required

Returns:

Name Type Description
x_values tuple[NP_ARRAY_OF_FLOAT_OR_INT, NP_ARRAY_OF_FLOAT_OR_INT, NP_ARRAY_OF_FLOAT_OR_INT] | tuple[PINT_NUMPY_ARRAY, PINT_NUMPY_ARRAY, PINT_NUMPY_ARRAY]

x-values (for a plot)

y_values_lower tuple[NP_ARRAY_OF_FLOAT_OR_INT, NP_ARRAY_OF_FLOAT_OR_INT, NP_ARRAY_OF_FLOAT_OR_INT] | tuple[PINT_NUMPY_ARRAY, PINT_NUMPY_ARRAY, PINT_NUMPY_ARRAY]

y-values for the lower-bound (of a plume plot)

y_values_upper tuple[NP_ARRAY_OF_FLOAT_OR_INT, NP_ARRAY_OF_FLOAT_OR_INT, NP_ARRAY_OF_FLOAT_OR_INT] | tuple[PINT_NUMPY_ARRAY, PINT_NUMPY_ARRAY, PINT_NUMPY_ARRAY]

y-values for the upper-bound (of a plume plot)

Raises:

Type Description
TypeError

unit_aware is not False and unit_var or time_units is None.

MissingOptionalDependencyError

unit_aware is True and pint is not installed.

Source code in src/pandas_openscm/plotting.py
def get_values_plume(  # noqa: PLR0913
    pdf: pd.DataFrame,
    *,
    quantiles: tuple[float, float],
    quantile_var: str,
    unit_aware: bool | pint.UnitRegistry,
    unit_var: str | None,
    time_units: str | None,
) -> (
    tuple[NP_ARRAY_OF_FLOAT_OR_INT, NP_ARRAY_OF_FLOAT_OR_INT, NP_ARRAY_OF_FLOAT_OR_INT]
    | tuple[PINT_NUMPY_ARRAY, PINT_NUMPY_ARRAY, PINT_NUMPY_ARRAY]
):
    """
    Get values for plotting a line

    Parameters
    ----------
    pdf
        [pd.DataFrame][pandas.DataFrame] from which to get the values

    quantiles
        Quantiles to get from `pdf`

    quantile_var
        Variable/column in the multi-index which stores information
        about the quantile that each timeseries represents.

    unit_aware
        Should the values be unit-aware?

        If `True`, we use the default application registry
        (retrieved with [pint.get_application_registry][]).
        Otherwise, a [pint.UnitRegistry][] can be supplied and will be used.

    unit_var
        Variable/column in the multi-index which stores information
        about the unit of each timeseries.

    time_units
        Units of the time axis.

    Returns
    -------
    x_values :
        x-values (for a plot)

    y_values_lower :
        y-values for the lower-bound (of a plume plot)

    y_values_upper :
        y-values for the upper-bound (of a plume plot)

    Raises
    ------
    TypeError
        `unit_aware` is not `False` and `unit_var` or `time_units` is `None`.

    MissingOptionalDependencyError
        `unit_aware` is `True`
        and [pint](https://pint.readthedocs.io/) is not installed.
    """
    res_no_units = (
        pdf.columns.values.squeeze(),
        pdf.loc[
            pdf.index.get_level_values(quantile_var).isin({quantiles[0]})
        ].values.squeeze(),
        pdf.loc[
            pdf.index.get_level_values(quantile_var).isin({quantiles[1]})
        ].values.squeeze(),
    )
    if not unit_aware:
        return res_no_units

    if unit_var is None:
        msg = "If `unit_aware` != False, then `unit_var` must not be `None`"
        raise TypeError(msg)

    if time_units is None:
        msg = "If `unit_aware` != False, then `time_units` must not be `None`"
        raise TypeError(msg)

    if isinstance(unit_aware, bool):
        try:
            import pint  # noqa: PLC0415
        except ImportError as exc:
            raise MissingOptionalDependencyError(  # noqa: TRY003
                "get_values_plume(..., unit_aware=True, ...)", requirement="pint"
            ) from exc

        ur = pint.get_application_registry()  # type: ignore[no-untyped-call] # pint typing limited

    else:
        ur = unit_aware

    unit = extract_single_unit(pdf, unit_var)
    res = (
        res_no_units[0] * ur(time_units),
        res_no_units[1] * ur(unit),
        res_no_units[2] * ur(unit),
    )

    return res

plot_plume_after_calculating_quantiles_func #

plot_plume_after_calculating_quantiles_func(
    pdf: DataFrame,
    ax: Axes | None = None,
    *,
    quantile_over: str | list[str],
    quantiles_plumes: QUANTILES_PLUMES_LIKE = (
        (0.5, 0.7),
        ((0.05, 0.95), 0.2),
    ),
    quantile_var_label: str | None = None,
    quantile_legend_round: int = 2,
    hue_var: str = "scenario",
    hue_var_label: str | None = None,
    palette: PALETTE_LIKE[Any] | None = None,
    warn_on_palette_value_missing: bool = True,
    style_var: str = "variable",
    style_var_label: str | None = None,
    dashes: dict[Any, str | tuple[float, tuple[float, ...]]]
    | None = None,
    warn_on_dashes_value_missing: bool = True,
    linewidth: float = 3.0,
    unit_var: str = "unit",
    unit_aware: bool | UnitRegistry = False,
    time_units: str | None = None,
    x_label: str | None = "time",
    y_label: str | bool | None = True,
    warn_infer_y_label_with_multi_unit: bool = True,
    create_legend: Callable[
        [Axes, list[Artist]], None
    ] = create_legend_default,
    observed: bool = True,
) -> Axes

Plot a plume plot, calculating the required quantiles first

Parameters:

Name Type Description Default
pdf DataFrame

pd.DataFrame to use for plotting

It must contain quantiles already. For data without quantiles, please see plot_plume_after_calculating_quantiles_func.

required
ax Axes | None

Axes on which to plot.

If not supplied, a new axes is created.

None
quantile_over str | list[str]

Variable(s)/column(s) over which to calculate the quantiles.

The data is grouped by all columns except quantile_over when calculating the quantiles.

required
quantiles_plumes QUANTILES_PLUMES_LIKE

Quantiles to plot in each plume.

If the first element of each tuple is a tuple, a plume is plotted between the given quantiles. Otherwise, if the first element is a plain float, a line is plotted for the given quantile.

((0.5, 0.7), ((0.05, 0.95), 0.2))
quantile_var_label str | None

Label to use as the header for the quantile section in the legend

None
quantile_legend_round int

Rounding to apply to quantile values when creating the legend

2
hue_var str

Variable to use for grouping data into different colour groups

'scenario'
hue_var_label str | None

Label to use as the header for the hue/colour section in the legend

None
palette PALETTE_LIKE[Any] | None

Colour to use for the different groups in the data.

If any groups are not included in palette, they are auto-filled.

None
warn_on_palette_value_missing bool

Should a warning be emitted if there are values missing from palette?

True
style_var str

Variable to use for grouping data into different (line)style groups

'variable'
style_var_label str | None

Label to use as the header for the style section in the legend

None
dashes dict[Any, str | tuple[float, tuple[float, ...]]] | None

Dash/linestyle to use for the different groups in the data.

If any groups are not included in dashes, they are auto-filled.

None
warn_on_dashes_value_missing bool

Should a warning be emitted if there are values missing from dashes?

True
linewidth float

Width to use for plotting lines.

3.0
unit_var str

Variable/column in the multi-index which stores information about the unit of each timeseries.

'unit'
unit_aware bool | UnitRegistry

Should the plot be done in a unit-aware way?

If True, we use the default application registry (retrieved with pint.get_application_registry). Otherwise, a pint.UnitRegistry can be supplied and will be used.

For details, see matplotlib and pint support plotting with units (stable docs, last version that we checked at the time of writing).

False
time_units str | None

Units of the time axis.

These are required if unit_aware is not False.

None
x_label str | None

Label to apply to the x-axis.

If None, no label will be applied.

'time'
y_label str | bool | None

Label to apply to the y-axis.

If True, we will try and infer the y-label based on the data's units.

If None, no label will be applied.

True
warn_infer_y_label_with_multi_unit bool

Should a warning be raised if we try to infer the y-unit but the data has more than one unit?

True
create_legend Callable[[Axes, list[Artist]], None]

Function to use to create the legend.

This allows the user to have full control over the creation of the legend.

create_legend_default
observed bool True

Returns:

Type Description
Axes

Axes on which the data was plotted

Source code in src/pandas_openscm/plotting.py
def plot_plume_after_calculating_quantiles_func(  # noqa: PLR0913
    pdf: pd.DataFrame,
    ax: matplotlib.axes.Axes | None = None,
    *,
    quantile_over: str | list[str],
    quantiles_plumes: QUANTILES_PLUMES_LIKE = (
        (0.5, 0.7),
        ((0.05, 0.95), 0.2),
    ),
    quantile_var_label: str | None = None,
    quantile_legend_round: int = 2,
    hue_var: str = "scenario",
    hue_var_label: str | None = None,
    palette: PALETTE_LIKE[Any] | None = None,
    warn_on_palette_value_missing: bool = True,
    style_var: str = "variable",
    style_var_label: str | None = None,
    dashes: dict[Any, str | tuple[float, tuple[float, ...]]] | None = None,
    warn_on_dashes_value_missing: bool = True,
    linewidth: float = 3.0,
    unit_var: str = "unit",
    unit_aware: bool | pint.UnitRegistry = False,
    time_units: str | None = None,
    x_label: str | None = "time",
    y_label: str | bool | None = True,
    warn_infer_y_label_with_multi_unit: bool = True,
    create_legend: Callable[
        [matplotlib.axes.Axes, list[matplotlib.artist.Artist]], None
    ] = create_legend_default,
    observed: bool = True,
) -> matplotlib.axes.Axes:
    """
    Plot a plume plot, calculating the required quantiles first

    Parameters
    ----------
    pdf
        [pd.DataFrame][pandas.DataFrame] to use for plotting

        It must contain quantiles already.
        For data without quantiles, please see
        [plot_plume_after_calculating_quantiles_func][(m).].

    ax
        Axes on which to plot.

        If not supplied, a new axes is created.

    quantile_over
        Variable(s)/column(s) over which to calculate the quantiles.

        The data is grouped by all columns except `quantile_over`
        when calculating the quantiles.

    quantiles_plumes
        Quantiles to plot in each plume.

        If the first element of each tuple is a tuple,
        a plume is plotted between the given quantiles.
        Otherwise, if the first element is a plain float,
        a line is plotted for the given quantile.

    quantile_var_label
        Label to use as the header for the quantile section in the legend

    quantile_legend_round
        Rounding to apply to quantile values when creating the legend

    hue_var
        Variable to use for grouping data into different colour groups

    hue_var_label
        Label to use as the header for the hue/colour section in the legend

    palette
        Colour to use for the different groups in the data.

        If any groups are not included in `palette`,
        they are auto-filled.

    warn_on_palette_value_missing
        Should a warning be emitted if there are values missing from `palette`?

    style_var
        Variable to use for grouping data into different (line)style groups

    style_var_label
        Label to use as the header for the style section in the legend

    dashes
        Dash/linestyle to use for the different groups in the data.

        If any groups are not included in `dashes`,
        they are auto-filled.

    warn_on_dashes_value_missing
        Should a warning be emitted if there are values missing from `dashes`?

    linewidth
        Width to use for plotting lines.

    unit_var
        Variable/column in the multi-index which stores information
        about the unit of each timeseries.

    unit_aware
        Should the plot be done in a unit-aware way?

        If `True`, we use the default application registry
        (retrieved with [pint.get_application_registry][]).
        Otherwise, a [pint.UnitRegistry][] can be supplied and will be used.

        For details, see matplotlib and pint support plotting with units
        ([stable docs](https://pint.readthedocs.io/en/stable/user/plotting.html),
        [last version that we checked at the time of writing](https://pint.readthedocs.io/en/0.24.4/user/plotting.html)).

    time_units
        Units of the time axis.

        These are required if `unit_aware` is not `False`.

    x_label
        Label to apply to the x-axis.

        If `None`, no label will be applied.

    y_label
        Label to apply to the y-axis.

        If `True`, we will try and infer the y-label based on the data's units.

        If `None`, no label will be applied.

    warn_infer_y_label_with_multi_unit
        Should a warning be raised if we try to infer the y-unit
        but the data has more than one unit?

    create_legend
        Function to use to create the legend.

        This allows the user to have full control over the creation of the legend.

    observed
        Passed to [pd.DataFrame.groupby][pandas.DataFrame.groupby].

    Returns
    -------
    :
        Axes on which the data was plotted
    """
    quantile_var = "quantile"
    pdf_q = fix_index_name_after_groupby_quantile(
        groupby_except(pdf, quantile_over).quantile(get_quantiles(quantiles_plumes)),
        new_name=quantile_var,
        copy=False,
    )

    return plot_plume_func(
        pdf=pdf_q,
        ax=ax,
        quantiles_plumes=quantiles_plumes,
        quantile_var=quantile_var,
        quantile_var_label=quantile_var_label,
        quantile_legend_round=quantile_legend_round,
        hue_var=hue_var,
        hue_var_label=hue_var_label,
        palette=palette,
        warn_on_palette_value_missing=warn_on_palette_value_missing,
        style_var=style_var,
        style_var_label=style_var_label,
        dashes=dashes,
        warn_on_dashes_value_missing=warn_on_dashes_value_missing,
        linewidth=linewidth,
        unit_var=unit_var,
        unit_aware=unit_aware,
        time_units=time_units,
        x_label=x_label,
        y_label=y_label,
        warn_infer_y_label_with_multi_unit=warn_infer_y_label_with_multi_unit,
        create_legend=create_legend,
        observed=observed,
    )

plot_plume_func #

plot_plume_func(
    pdf: DataFrame,
    quantiles_plumes: QUANTILES_PLUMES_LIKE,
    ax: Axes | None = None,
    *,
    quantile_var: str = "quantile",
    quantile_var_label: str | None = None,
    quantile_legend_round: int = 3,
    hue_var: str = "scenario",
    hue_var_label: str | None = None,
    palette: PALETTE_LIKE[Any] | None = None,
    warn_on_palette_value_missing: bool = True,
    style_var: str = "variable",
    style_var_label: str | None = None,
    dashes: dict[Any, str | tuple[float, tuple[float, ...]]]
    | None = None,
    warn_on_dashes_value_missing: bool = True,
    linewidth: float = 2.0,
    unit_var: str = "unit",
    unit_aware: bool | UnitRegistry = False,
    time_units: str | None = None,
    x_label: str | None = "time",
    y_label: str | bool | None = True,
    warn_infer_y_label_with_multi_unit: bool = True,
    create_legend: Callable[
        [Axes, list[Artist]], None
    ] = create_legend_default,
    observed: bool = True,
) -> Axes

Plot a plume plot

Parameters:

Name Type Description Default
pdf DataFrame

pd.DataFrame to use for plotting

It must contain quantiles already. For data without quantiles, please see plot_plume_after_calculating_quantiles_func.

required
quantiles_plumes QUANTILES_PLUMES_LIKE

Quantiles to plot in each plume.

If the first element of each tuple is a tuple, a plume is plotted between the given quantiles. Otherwise, if the first element is a plain float, a line is plotted for the given quantile.

required
ax Axes | None

Axes on which to plot.

If not supplied, a new axes is created.

None
quantile_var str

Variable/column in the multi-index which stores information about the quantile that each timeseries represents.

'quantile'
quantile_var_label str | None

Label to use as the header for the quantile section in the legend

None
quantile_legend_round int

Rounding to apply to quantile values when creating the legend

3
hue_var str

Variable to use for grouping data into different colour groups

'scenario'
hue_var_label str | None

Label to use as the header for the hue/colour section in the legend

None
palette PALETTE_LIKE[Any] | None

Colour to use for the different groups in the data.

If any groups are not included in palette, they are auto-filled.

None
warn_on_palette_value_missing bool

Should a warning be emitted if there are values missing from palette?

True
style_var str

Variable to use for grouping data into different (line)style groups

'variable'
style_var_label str | None

Label to use as the header for the style section in the legend

None
dashes dict[Any, str | tuple[float, tuple[float, ...]]] | None

Dash/linestyle to use for the different groups in the data.

If any groups are not included in dashes, they are auto-filled.

None
warn_on_dashes_value_missing bool

Should a warning be emitted if there are values missing from dashes?

True
linewidth float

Width to use for plotting lines.

2.0
unit_var str

Variable/column in the multi-index which stores information about the unit of each timeseries.

'unit'
unit_aware bool | UnitRegistry

Should the plot be done in a unit-aware way?

If True, we use the default application registry (retrieved with pint.get_application_registry). Otherwise, a pint.UnitRegistry can be supplied and will be used.

For details, see matplotlib and pint support plotting with units (stable docs, last version that we checked at the time of writing).

False
time_units str | None

Units of the time axis of the data.

These are required if unit_aware is not False.

None
x_label str | None

Label to apply to the x-axis.

If None, no label will be applied.

'time'
y_label str | bool | None

Label to apply to the y-axis.

If True, we will try and infer the y-label based on the data's units.

If None, no label will be applied.

True
warn_infer_y_label_with_multi_unit bool

Should a warning be raised if we try to infer the y-unit but the data has more than one unit?

True
create_legend Callable[[Axes, list[Artist]], None]

Function to use to create the legend.

This allows the user to have full control over the creation of the legend.

create_legend_default
observed bool True

Returns:

Type Description
Axes

Axes on which the data was plotted

Source code in src/pandas_openscm/plotting.py
def plot_plume_func(  # noqa: PLR0913
    pdf: pd.DataFrame,
    quantiles_plumes: QUANTILES_PLUMES_LIKE,
    ax: matplotlib.axes.Axes | None = None,
    *,
    quantile_var: str = "quantile",
    quantile_var_label: str | None = None,
    quantile_legend_round: int = 3,
    hue_var: str = "scenario",
    hue_var_label: str | None = None,
    palette: PALETTE_LIKE[Any] | None = None,
    warn_on_palette_value_missing: bool = True,
    style_var: str = "variable",
    style_var_label: str | None = None,
    dashes: dict[Any, str | tuple[float, tuple[float, ...]]] | None = None,
    warn_on_dashes_value_missing: bool = True,
    linewidth: float = 2.0,
    unit_var: str = "unit",
    unit_aware: bool | pint.UnitRegistry = False,
    time_units: str | None = None,
    x_label: str | None = "time",
    y_label: str | bool | None = True,
    warn_infer_y_label_with_multi_unit: bool = True,
    create_legend: Callable[
        [matplotlib.axes.Axes, list[matplotlib.artist.Artist]], None
    ] = create_legend_default,
    observed: bool = True,
) -> matplotlib.axes.Axes:
    """
    Plot a plume plot

    Parameters
    ----------
    pdf
        [pd.DataFrame][pandas.DataFrame] to use for plotting

        It must contain quantiles already.
        For data without quantiles, please see
        [plot_plume_after_calculating_quantiles_func][(m).].

    quantiles_plumes
        Quantiles to plot in each plume.

        If the first element of each tuple is a tuple,
        a plume is plotted between the given quantiles.
        Otherwise, if the first element is a plain float,
        a line is plotted for the given quantile.

    ax
        Axes on which to plot.

        If not supplied, a new axes is created.

    quantile_var
        Variable/column in the multi-index which stores information
        about the quantile that each timeseries represents.

    quantile_var_label
        Label to use as the header for the quantile section in the legend

    quantile_legend_round
        Rounding to apply to quantile values when creating the legend

    hue_var
        Variable to use for grouping data into different colour groups

    hue_var_label
        Label to use as the header for the hue/colour section in the legend

    palette
        Colour to use for the different groups in the data.

        If any groups are not included in `palette`,
        they are auto-filled.

    warn_on_palette_value_missing
        Should a warning be emitted if there are values missing from `palette`?

    style_var
        Variable to use for grouping data into different (line)style groups

    style_var_label
        Label to use as the header for the style section in the legend

    dashes
        Dash/linestyle to use for the different groups in the data.

        If any groups are not included in `dashes`,
        they are auto-filled.

    warn_on_dashes_value_missing
        Should a warning be emitted if there are values missing from `dashes`?

    linewidth
        Width to use for plotting lines.

    unit_var
        Variable/column in the multi-index which stores information
        about the unit of each timeseries.

    unit_aware
        Should the plot be done in a unit-aware way?

        If `True`, we use the default application registry
        (retrieved with [pint.get_application_registry][]).
        Otherwise, a [pint.UnitRegistry][] can be supplied and will be used.

        For details, see matplotlib and pint support plotting with units
        ([stable docs](https://pint.readthedocs.io/en/stable/user/plotting.html),
        [last version that we checked at the time of writing](https://pint.readthedocs.io/en/0.24.4/user/plotting.html)).

    time_units
        Units of the time axis of the data.

        These are required if `unit_aware` is not `False`.

    x_label
        Label to apply to the x-axis.

        If `None`, no label will be applied.

    y_label
        Label to apply to the y-axis.

        If `True`, we will try and infer the y-label based on the data's units.

        If `None`, no label will be applied.

    warn_infer_y_label_with_multi_unit
        Should a warning be raised if we try to infer the y-unit
        but the data has more than one unit?

    create_legend
        Function to use to create the legend.

        This allows the user to have full control over the creation of the legend.

    observed
        Passed to [pd.DataFrame.groupby][pandas.DataFrame.groupby].

    Returns
    -------
    :
        Axes on which the data was plotted
    """
    plotter = PlumePlotter.from_df(
        df=pdf,
        quantiles_plumes=quantiles_plumes,
        quantile_var=quantile_var,
        quantile_var_label=quantile_var_label,
        hue_var=hue_var,
        hue_var_label=hue_var_label,
        palette=palette,
        warn_on_palette_value_missing=warn_on_palette_value_missing,
        style_var=style_var,
        style_var_label=style_var_label,
        dashes=dashes,
        warn_on_dashes_value_missing=warn_on_dashes_value_missing,
        linewidth=linewidth,
        unit_var=unit_var,
        unit_aware=unit_aware,
        time_units=time_units,
        x_label=x_label,
        y_label=y_label,
        warn_infer_y_label_with_multi_unit=warn_infer_y_label_with_multi_unit,
        observed=observed,
    )

    ax = plotter.plot(
        ax=ax, create_legend=create_legend, quantile_legend_round=quantile_legend_round
    )

    return ax

same_shape_as_x_vals #

same_shape_as_x_vals(
    obj: SingleLinePlotter | SinglePlumePlotter,
    attribute: Attribute[Any],
    value: NP_ARRAY_OF_FLOAT_OR_INT | PINT_NUMPY_ARRAY,
) -> None

Validate that the received values are the same shape as obj.x_vals

Parameters:

Name Type Description Default
obj SingleLinePlotter | SinglePlumePlotter

Object on which we are peforming validation

required
attribute Attribute[Any]

Attribute which is being set

required
value NP_ARRAY_OF_FLOAT_OR_INT | PINT_NUMPY_ARRAY

Value which is being used to set attribute

required

Raises:

Type Description
AssertionError

value.shape is not the same as obj.x_vals.shape

Source code in src/pandas_openscm/plotting.py
def same_shape_as_x_vals(
    obj: SingleLinePlotter | SinglePlumePlotter,
    attribute: attr.Attribute[Any],
    value: NP_ARRAY_OF_FLOAT_OR_INT | PINT_NUMPY_ARRAY,
) -> None:
    """
    Validate that the received values are the same shape as `obj.x_vals`

    Parameters
    ----------
    obj
        Object on which we are peforming validation

    attribute
        Attribute which is being set

    value
        Value which is being used to set `attribute`

    Raises
    ------
    AssertionError
        `value.shape` is not the same as `obj.x_vals.shape`
    """
    if value.shape != obj.x_vals.shape:
        msg = (
            f"`{attribute.name}` must have the same shape as `x_vals`. "
            f"Received `y_vals` with shape {value.shape} "
            f"while `x_vals` has shape {obj.x_vals.shape}"
        )
        raise AssertionError(msg)