//  [python]
def plot_model_metric_sets(metrics_sets, x_key, y_key,
                           x_label=None, y_label=None,
                           primary_differentiator_key=None,
                           primary_differentiator_order=None,
                           secondary_differentiator_key=None,
                           secondary_differentiator_order=None,
                           special_cases=None, other_method_metrics=None,
                           palette=None, marker_styles=None, name=None):

    figure_name = saving.build_figure_name("model_metric_sets", name)

    if other_method_metrics:
        figure_name += "-other_methods"

    if not isinstance(metrics_sets, list):
        metrics_sets = [metrics_sets]

    if not palette:
        palette = style.STANDARD_PALETTE.copy()

    if not marker_styles:
        marker_styles = [
            "X",  # cross
            "s",  # square
            "D",  # diamond
            "o",  # circle
            "P",  # plus
            "^",  # upright triangle
            "p",  # pentagon
            "*",  # star
        ]

    figure = pyplot.figure(figsize=(9, 6))
    axis = figure.add_subplot(1, 1, 1)
    seaborn.utils.despine()

    axis.set_xlabel(x_label)
    axis.set_ylabel(y_label)

    colours = {}
    markers = {}

    for metrics_set in metrics_sets:

        x = numpy.array(metrics_set[x_key])
        y = numpy.array(metrics_set[y_key])

        if x.dtype == "object" or y.dtype == "object":
            continue

        x_mean = x.mean()
        x_ddof = 1 if x.size > 1 else 0
        x_sd = x.std(ddof=x_ddof)

        y_mean = y.mean()
        y_ddof = 1 if y.size > 1 else 0
        y_sd = y.std(ddof=y_ddof)

        colour_key = metrics_set[primary_differentiator_key]
        if colour_key in colours:
            colour = colours[colour_key]
        else:
            try:
                index = primary_differentiator_order.index(colour_key)
                colour = palette[index]
            except (ValueError, IndexError):
                colour = "black"
            colours[colour_key] = colour
            axis.errorbar(
                x=x_mean,
                y=y_mean,
                yerr=y_sd,
                xerr=x_sd,
                capsize=2,
                linestyle="",
                color=colour,
                label=colour_key,
                markersize=7
            )

        marker_key = metrics_set[secondary_differentiator_key]
        if marker_key in markers:
            marker = markers[marker_key]
        else:
            try:
                index = secondary_differentiator_order.index(marker_key)
                marker = marker_styles[index]
            except (ValueError, IndexError):
                marker = None
            markers[marker_key] = marker
            axis.errorbar(
                x_mean, y_mean,
                color="black", marker=marker, linestyle="none",
                label=marker_key
            )

        errorbar_colour = colour
        darker_colour = seaborn.dark_palette(colour, n_colors=4)[2]

        special_case_changes = special_cases.get(colour_key, {})
        special_case_changes.update(
            special_cases.get(marker_key, {})
        )

        for object_name, object_change in special_case_changes.items():

            if object_name == "errorbar_colour":
                if object_change == "darken":
                    errorbar_colour = darker_colour

        axis.errorbar(
            x=x_mean,
            y=y_mean,
            yerr=y_sd,
            xerr=x_sd,
            ecolor=errorbar_colour,
            capsize=2,
            color=colour,
            marker=marker,
            markeredgecolor=darker_colour,
            markersize=7
        )

    baseline_line_styles = [
        "dashed",
        "dotted",
        "dashdot",
        "solid"
    ]
    legend_outside = False

    if other_method_metrics:
        for method_name, metric_values in other_method_metrics.items():

            x_values = metric_values.get(x_key, None)
            y_values = metric_values.get(y_key, None)

            if not y_values:
                continue

            y = numpy.array(y_values)
            y_mean = y.mean()

            if y.shape[0] > 1:
                y_sd = y.std(ddof=1)
            else:
                y_sd = None

            if x_values:
                legend_outside = True

                x = numpy.array(x_values)
                x_mean = x.mean()

                if x.shape[0] > 1:
                    x_sd = x.std(ddof=1)
                else:
                    x_sd = None

                axis.errorbar(
                    x=x_mean,
                    y=y_mean,
                    yerr=y_sd,
                    xerr=x_sd,
                    ecolor=style.STANDARD_PALETTE[-1],
                    color=style.STANDARD_PALETTE[-1],
                    capsize=2,
                    linestyle="none",
                    label=method_name
                )

            else:
                line_style = baseline_line_styles.pop(0)

                axis.axhline(
                    y=y_mean,
                    color=style.STANDARD_PALETTE[-1],
                    linestyle=line_style,
                    label=method_name,
                    zorder=-1
                )

                if y_sd is not None:
                    axis.axhspan(
                        ymin=y_mean - y_sd,
                        ymax=y_mean + y_sd,
                        facecolor=style.STANDARD_PALETTE[-1],
                        alpha=0.1,
                        edgecolor=None,
                        label=method_name,
                        zorder=-2
                    )

    if len(metrics_sets) > 1:

        order = primary_differentiator_order + secondary_differentiator_order
        handles, labels = axis.get_legend_handles_labels()

        label_handles = {}

        for label, handle in zip(labels, handles):
            label_handles.setdefault(label, [])
            label_handles[label].append(handle)

        labels, handles = [], []

        for label, handle_set in label_handles.items():
            labels.append(label)
            handles.append(tuple(handle_set))

        labels, handles = zip(*sorted(
            zip(labels, handles),
            key=lambda l:
                [order.index(l[0]), l[0]] if l[0] in order
                else [len(order), l[0]]
        ))

        if legend_outside:
            legend_keywords = {
                "loc": 'center left',
                "bbox_to_anchor": (1.05, 0, .3, 1),
                "ncol": 1,
                # "mode": "expand",
                "borderaxespad": 0
            }
        else:
            legend_keywords = {"loc": "best"}

        axis.legend(handles, labels, **legend_keywords)

    return figure, figure_name