Example #1
0
def mean_trace_scatter(
    y: np.ndarray,
    trace_color: Tuple[int] = COLORS.STEELBLUE.value,
    legend_label: str = "mean",
    hover_labels: Optional[List[str]] = None,
) -> go.Scatter:
    """Creates a graph object for trace of the mean of the given series across
    runs.

    Args:
        y: (r x t) array with results from  r runs and t trials.
        trace_color: tuple of 3 int values representing an RGB color.
            Defaults to blue.
        legend_label: label for this trace.
        hover_labels: optional, text to show on hover; list where the i-th value
            corresponds to the i-th value in the value of the `y` argument.

    Returns:
        go.Scatter: plotly graph object
    """
    return go.Scatter(
        name=legend_label,
        legendgroup=legend_label,
        x=np.arange(1, y.shape[1] + 1),
        y=np.mean(y, axis=0),
        mode="lines",
        line={"color": rgba(trace_color)},
        fillcolor=rgba(trace_color, 0.3),
        fill="tonexty",
        text=hover_labels,
    )
Example #2
0
File: trace.py Project: jshuadvd/Ax
def mean_trace_scatter(
    y: np.ndarray,
    trace_color: Tuple[int] = COLORS.STEELBLUE.value,
    legend_label: str = "mean",
) -> go.Scatter:
    """Creates a graph object for trace of the mean of the given series across
    runs.

    Args:
        y: (r x t) array with results from  r runs and t trials.
        trace_color: tuple of 3 int values representing an RGB color.
            Defaults to blue.
        legend_label: label for this trace

    Returns:
        go.Scatter: plotly graph object
    """
    return go.Scatter(
        name=legend_label,
        legendgroup=legend_label,
        x=np.arange(1, y.shape[1] + 1),
        y=np.mean(y, axis=0),
        mode="lines",
        line={"color": rgba(trace_color)},
        fillcolor=rgba(trace_color, 0.3),
        fill="tonexty",
    )
Example #3
0
def model_transitions_scatter(
    model_transitions: List[int],
    y_range: List[float],
    generator_change_color: Tuple[int] = COLORS.TEAL.value,
) -> List[go.Scatter]:
    """Creates a graph object for the line(s) representing generator changes.

    Args:
        model_transitions: iterations, before which generators
            changed
        y_range: upper and lower values of the y-range of the plot
        generator_change_color: tuple of 3 int values representing
            an RGB color. Defaults to orange.

    Returns:
        go.Scatter: plotly graph objects for the lines representing generator
            changes
    """
    if len(y_range) != 2:  # pragma: no cover
        raise ValueError("y_range should have two values, lower and upper.")
    data: List[go.Scatter] = []
    for change in model_transitions:
        data.append(
            go.Scatter(
                x=[change] * 2,
                y=y_range,
                mode="lines",
                line={
                    "dash": "dash",
                    "color": rgba(generator_change_color)
                },
                name="Generator change",
            ))
    return data
Example #4
0
def optimum_objective_scatter(
        optimum: float,
        num_iterations: int,
        optimum_color: Tuple[int] = COLORS.ORANGE.value) -> go.Scatter:
    """Creates a graph object for the line representing optimal objective.

    Args:
        optimum: value of the optimal objective
        num_iterations: how many trials were in the optimization (used to
            determine the width of the plot)
        trace_color: tuple of 3 int values representing an RGB color.
            Defaults to orange.

    Returns:
        go.Scatter: plotly graph objects for the optimal objective line
    """
    return go.Scatter(
        x=[1, num_iterations],
        y=[optimum] * 2,
        mode="lines",
        line={
            "dash": "dash",
            "color": rgba(optimum_color)
        },
        name="Optimum",
    )
Example #5
0
def mean_markers_scatter(
    y: np.ndarray,
    marker_color: Tuple[int] = COLORS.LIGHT_PURPLE.value,
    legend_label: str = "",
    hover_labels: Optional[List[str]] = None,
) -> go.Scatter:
    """Creates a graph object for trace of the mean of the given series across
    runs, with errorbars.

    Args:
        y: (r x t) array with results from  r runs and t trials.
        trace_color: tuple of 3 int values representing an RGB color.
            Defaults to light purple.
        legend_label: label for this trace.
        hover_labels: optional, text to show on hover; list where the i-th value
            corresponds to the i-th value in the value of the `y` argument.

    Returns:
        go.Scatter: plotly graph object
    """
    mean = np.mean(y, axis=0)
    sem = np.std(y, axis=0) / np.sqrt(y.shape[0])
    return go.Scatter(
        name=legend_label,
        x=np.arange(1, y.shape[1] + 1),
        y=mean,
        error_y={
            "type": "data",
            "array": sem,
            "visible": True,
        },
        mode="markers",
        marker={"color": rgba(marker_color)},
        text=hover_labels,
    )
Example #6
0
def plot_bandit_rollout(experiment: Experiment) -> AxPlotConfig:
    """Plot bandit rollout from ane experiement."""

    categories: List[str] = []
    arms: Dict[str, Dict[str, Any]] = {}

    data = []

    index = 0
    for trial in sorted(experiment.trials.values(),
                        key=lambda trial: trial.index):
        if not isinstance(trial, BatchTrial):
            raise ValueError(
                "Bandit rollout graph is not supported for BaseTrial."
            )  # pragma: no cover

        category = f"Round {trial.index}"
        categories.append(category)

        for arm, weight in trial.normalized_arm_weights(total=100).items():
            if arm.name not in arms:
                arms[arm.name] = {
                    "index": index,
                    "name": arm.name,
                    "x": [],
                    "y": [],
                    "text": [],
                }
                index += 1

            arms[arm.name]["x"].append(category)
            arms[arm.name]["y"].append(weight)
            arms[arm.name]["text"].append("{:.2f}%".format(weight))

    for key in arms.keys():
        data.append(arms[key])

    # pyre-fixme[6]: Expected `typing.Tuple[...g.Tuple[int, int, int]`.
    colors = [rgba(c) for c in MIXED_SCALE]
    config = {"data": data, "categories": categories, "colors": colors}

    return AxPlotConfig(config, plot_type=AxPlotTypes.BANDIT_ROLLOUT)
Example #7
0
File: trace.py Project: zorrock/Ax
def sem_range_scatter(
    y: np.ndarray,
    trace_color: Tuple[int] = COLORS.STEELBLUE.value,
    legend_label: str = "",
) -> Tuple[go.Scatter]:
    """Creates a graph object for trace of mean +/- 2 SEMs for y, across runs.

    Args:
        y: (r x t) array with results from  r runs and t trials.
        trace_color: tuple of 3 int values representing an RGB color.
            Defaults to blue.
        legend_label: Label for the legend group.

    Returns:
        Tuple[go.Scatter]: plotly graph objects for lower and upper bounds
    """
    mean = np.mean(y, axis=0)
    sem = np.std(y, axis=0) / np.sqrt(y.shape[0])
    return (
        go.
        Scatter(  # pyre-ignore[16]: `plotly.graph_objs` has no attr. `Scatter`
            x=np.arange(1, y.shape[1] + 1),
            y=mean - 2 * sem,
            legendgroup=legend_label,
            mode="lines",
            line={"width": 0},
            showlegend=False,
            hoverinfo="none",
        ),
        go.
        Scatter(  # pyre-ignore[16]: `plotly.graph_objs` has no attr. `Scatter`
            x=np.arange(1, y.shape[1] + 1),
            y=mean + 2 * sem,
            legendgroup=legend_label,
            mode="lines",
            line={"width": 0},
            fillcolor=rgba(trace_color, 0.3),
            fill="tonexty",
            showlegend=False,
            hoverinfo="none",
        ),
    )
Example #8
0
def scatter_plot_with_pareto_frontier_plotly(
    Y: np.ndarray,
    Y_pareto: Optional[np.ndarray],
    metric_x: Optional[str],
    metric_y: Optional[str],
    reference_point: Optional[Tuple[float, float]],
    minimize: Optional[Union[bool, Tuple[bool, bool]]] = True,
) -> go.Figure:
    """Plots a scatter of all points in ``Y`` for ``metric_x`` and ``metric_y``
    with a reference point and Pareto frontier from ``Y_pareto``.

    Points in the scatter are colored in a gradient representing their trial index,
    with metric_x on x-axis and metric_y on y-axis. Reference point is represented
    as a star and Pareto frontier –– as a line. The frontier connects to the reference
    point via projection lines.

    NOTE: Both metrics should have the same minimization setting, passed as `minimize`.

    Args:
        Y: Array of outcomes, of which the first two will be plotted.
        Y_pareto: Array of Pareto-optimal points, first two outcomes in which will be
            plotted.
        metric_x: Name of first outcome in ``Y``.
        metric_Y: Name of second outcome in ``Y``.
        reference_point: Reference point for ``metric_x`` and ``metric_y``.
        minimize: Whether the two metrics in the plot are being minimized or maximized.
    """
    title = "Observed metric values"
    if isinstance(minimize, bool):
        minimize = (minimize, minimize)
    Xs = Y[:, 0]
    Ys = Y[:, 1]

    experimental_points_scatter = [
        go.Scatter(
            x=Xs,
            y=Ys,
            mode="markers",
            marker={
                "color": np.linspace(0, 100, int(len(Xs) * 1.05)),
                "colorscale": "magma",
                "colorbar": {
                    "tickvals": [0, 50, 100],
                    "ticktext": [
                        1,
                        "iteration",
                        len(Xs),
                    ],
                },
            },
            name="Experimental points",
        )
    ]
    # No Pareto frontier is drawn if none is provided, or if the frontier consists of
    # a single point and no reference points are provided.
    if Y_pareto is None or (len(Y_pareto) == 1 and reference_point is None):
        # `Y_pareto` input was not specified
        range_x = extend_range(lower=min(Y[:, 0]), upper=max(Y[:, 0]))
        range_y = extend_range(lower=min(Y[:, 1]), upper=max(Y[:, 1]))
        pareto_step = reference_point_lines = reference_point_star = []
    else:
        title += " with Pareto frontier"
        if reference_point:
            if minimize is None:
                minimize = tuple(reference_point[i] >= max(Y_pareto[:, i])
                                 for i in range(2))
            reference_point_star = [
                go.Scatter(
                    x=[reference_point[0]],
                    y=[reference_point[1]],
                    mode="markers",
                    marker={
                        "color": rgba(COLORS.STEELBLUE.value),
                        "size": 25,
                        "symbol": "star",
                    },
                )
            ]
            extra_point_x = min(Y_pareto[:, 0]) if minimize[0] else max(
                Y_pareto[:, 0])
            reference_point_line_1 = go.Scatter(
                x=[extra_point_x, reference_point[0]],
                y=[reference_point[1], reference_point[1]],
                mode="lines",
                marker={"color": rgba(COLORS.STEELBLUE.value)},
            )
            extra_point_y = min(Y_pareto[:, 1]) if minimize[1] else max(
                Y_pareto[:, 1])
            reference_point_line_2 = go.Scatter(
                x=[reference_point[0], reference_point[0]],
                y=[extra_point_y, reference_point[1]],
                mode="lines",
                marker={"color": rgba(COLORS.STEELBLUE.value)},
            )
            reference_point_lines = [
                reference_point_line_1, reference_point_line_2
            ]
            Y_pareto_with_extra = np.concatenate(
                (
                    [[extra_point_x, reference_point[1]]],
                    Y_pareto,
                    [[reference_point[0], extra_point_y]],
                ),
                axis=0,
            )
            pareto_step = [
                go.Scatter(
                    x=Y_pareto_with_extra[:, 0],
                    y=Y_pareto_with_extra[:, 1],
                    mode="lines",
                    line_shape="hv",
                    marker={"color": rgba(COLORS.STEELBLUE.value)},
                )
            ]

            range_x = (extend_range(lower=min(Y_pareto[:, 0]),
                                    upper=reference_point[0]) if minimize[0]
                       else extend_range(lower=reference_point[0],
                                         upper=max(Y_pareto[:, 0])))
            range_y = (extend_range(lower=min(Y_pareto[:, 1]),
                                    upper=reference_point[1]) if minimize[1]
                       else extend_range(lower=reference_point[1],
                                         upper=max(Y_pareto[:, 1])))
        else:  # Reference point was not specified
            pareto_step = [
                go.Scatter(
                    x=Y_pareto[:, 0],
                    y=Y_pareto[:, 1],
                    mode="lines",
                    line_shape="hv",
                    marker={"color": rgba(COLORS.STEELBLUE.value)},
                )
            ]
            reference_point_lines = reference_point_star = []

            range_x = extend_range(lower=min(Y_pareto[:, 0]),
                                   upper=max(Y_pareto[:, 0]))
            range_y = extend_range(lower=min(Y_pareto[:, 1]),
                                   upper=max(Y_pareto[:, 1]))
    layout = go.Layout(
        title=title,
        showlegend=False,
        xaxis={
            "title": metric_x or "",
            "range": range_x
        },
        yaxis={
            "title": metric_y or "",
            "range": range_y
        },
    )
    return go.Figure(
        layout=layout,
        data=pareto_step + reference_point_lines +
        experimental_points_scatter + reference_point_star,
    )
Example #9
0
def plot_bandit_rollout(experiment: Experiment) -> AxPlotConfig:
    """Plot bandit rollout from ane experiement."""

    categories: List[str] = []
    arms: Dict[str, Dict[str, Any]] = {}

    data = []

    index = 0
    for trial in sorted(experiment.trials.values(),
                        key=lambda trial: trial.index):
        if not isinstance(trial, BatchTrial):
            raise ValueError(
                "Bandit rollout graph is not supported for BaseTrial."
            )  # pragma: no cover

        category = f"Round {trial.index}"
        categories.append(category)

        for arm, weight in trial.normalized_arm_weights(total=100).items():
            if arm.name not in arms:
                arms[arm.name] = {
                    "index": index,
                    "name": arm.name,
                    "x": [],
                    "y": [],
                    "text": [],
                }
                index += 1

            arms[arm.name]["x"].append(category)
            arms[arm.name]["y"].append(weight)
            arms[arm.name]["text"].append("{:.2f}%".format(weight))

    for key in arms.keys():
        data.append(arms[key])

    # pyre-fixme[6]: Expected `typing.Tuple[...g.Tuple[int, int, int]`.
    colors = [rgba(c) for c in MIXED_SCALE]

    layout = go.Layout(  # pyre-ignore[16]
        title="Rollout Process<br>Bandit Weight Graph",
        xaxis={
            "title": "Rounds",
            "zeroline": False,
            "categoryorder": "array",
            "categoryarray": categories,
        },
        yaxis={"title": "Percent", "showline": False},
        barmode="stack",
        showlegend=False,
        margin={"r": 40},
    )

    bandit_config = {"type": "bar", "hoverinfo": "name+text", "width": 0.5}

    bandits = [
        dict(bandit_config,
             marker={"color": colors[d["index"] % len(colors)]},
             **d) for d in data
    ]
    for bandit in bandits:
        del bandit[
            "index"]  # Have to delete index or figure creation causes error
    fig = go.Figure(data=bandits, layout=layout)  # pyre-ignore[16]

    return AxPlotConfig(data=fig, plot_type=AxPlotTypes.GENERIC)
Example #10
0
def _get_single_pareto_trace(
    frontier: ParetoFrontierResults,
    CI_level: float,
    legend_label: str = "mean",
    trace_color: Tuple[int] = COLORS.STEELBLUE.value,
    show_parameterization_on_hover: bool = True,
) -> go.Scatter:
    primary_means = frontier.means[frontier.primary_metric]
    primary_sems = frontier.sems[frontier.primary_metric]
    secondary_means = frontier.means[frontier.secondary_metric]
    secondary_sems = frontier.sems[frontier.secondary_metric]
    absolute_metrics = frontier.absolute_metrics
    all_metrics = frontier.means.keys()
    if frontier.arm_names is None:
        arm_names = [
            f"Parameterization {i}" for i in range(len(frontier.param_dicts))
        ]
    else:
        arm_names = [f"Arm {name}" for name in frontier.arm_names]

    if CI_level is not None:
        Z = 0.5 * norm.ppf(1 - (1 - CI_level) / 2)
    else:
        Z = None

    labels = []

    for i, param_dict in enumerate(frontier.param_dicts):
        label = f"<b>{arm_names[i]}</b><br>"
        for metric in all_metrics:
            metric_lab = _make_label(
                mean=frontier.means[metric][i],
                sem=frontier.sems[metric][i],
                name=metric,
                is_relative=metric not in absolute_metrics,
                Z=Z,
            )
            label += metric_lab

        parameterization = (_format_dict(param_dict, "Parameterization")
                            if show_parameterization_on_hover else "")
        label += parameterization
        labels.append(label)
    return go.Scatter(
        name=legend_label,
        legendgroup=legend_label,
        x=secondary_means,
        y=primary_means,
        error_x={
            "type": "data",
            "array": Z * np.array(secondary_sems),
            "thickness": 2,
            "color": rgba(trace_color, CI_OPACITY),
        },
        error_y={
            "type": "data",
            "array": Z * np.array(primary_sems),
            "thickness": 2,
            "color": rgba(trace_color, CI_OPACITY),
        },
        mode="markers",
        text=labels,
        hoverinfo="text",
        marker={"color": rgba(trace_color)},
    )
Example #11
0
def optimization_times(
    fit_times: Dict[str, List[float]],
    gen_times: Dict[str, List[float]],
    title: str = "",
) -> AxPlotConfig:
    """Plots wall times for each method as a bar chart.

    Args:
        fit_times: A map from method name to a list of the model fitting times.
        gen_times: A map from method name to a list of the gen times.
        title: Title for this plot.

    Returns: AxPlotConfig with the plot
    """
    # Compute means and SEs
    methods = list(fit_times.keys())
    fit_res: Dict[str, Union[str, List[float]]] = {"name": "Fitting"}
    fit_res["mean"] = [np.mean(fit_times[m]) for m in methods]
    fit_res["2sems"] = [
        2 * np.std(fit_times[m]) / np.sqrt(len(fit_times[m])) for m in methods
    ]
    gen_res: Dict[str, Union[str, List[float]]] = {"name": "Generation"}
    gen_res["mean"] = [np.mean(gen_times[m]) for m in methods]
    gen_res["2sems"] = [
        2 * np.std(gen_times[m]) / np.sqrt(len(gen_times[m])) for m in methods
    ]
    total_mean: List[float] = []
    total_2sems: List[float] = []
    for m in methods:
        totals = np.array(fit_times[m]) + np.array(gen_times[m])
        total_mean.append(np.mean(totals))
        total_2sems.append(2 * np.std(totals) / np.sqrt(len(totals)))
    total_res: Dict[str, Union[str, List[float]]] = {
        "name": "Total",
        "mean": total_mean,
        "2sems": total_2sems,
    }

    # Construct plot
    data: List[go.Bar] = []

    for i, res in enumerate([fit_res, gen_res, total_res]):
        data.append(
            go.Bar(
                x=methods,
                y=res["mean"],
                text=res["name"],
                textposition="auto",
                error_y={
                    "type": "data",
                    "array": res["2sems"],
                    "visible": True
                },
                marker={
                    "color": rgba(DISCRETE_COLOR_SCALE[i]),
                    "line": {
                        "color": "rgb(0,0,0)",
                        "width": 1.0
                    },
                },
                opacity=0.6,
                name=res["name"],
            ))

    layout = go.Layout(
        title=title,
        showlegend=False,
        yaxis={"title": "Time"},
        xaxis={"title": "Method"},
    )

    return AxPlotConfig(data=go.Figure(layout=layout, data=data),
                        plot_type=AxPlotTypes.GENERIC)
Example #12
0
def plot_pareto_frontier(
    frontier: ParetoFrontierResults,
    CI_level: float = DEFAULT_CI_LEVEL,
    show_parameterization_on_hover: bool = True,
) -> AxPlotConfig:
    """Plot a Pareto frontier from a ParetoFrontierResults object.

    Args:
        frontier (ParetoFrontierResults): The results of the Pareto frontier
            computation.
        CI_level (float, optional): The confidence level, i.e. 0.95 (95%)
        show_parameterization_on_hover (bool, optional): If True, show the
            parameterization of the points on the frontier on hover.

    Returns:
        AEPlotConfig: The resulting Plotly plot definition.

    """
    trace = _get_single_pareto_trace(
        frontier=frontier,
        CI_level=CI_level,
        show_parameterization_on_hover=show_parameterization_on_hover,
    )

    shapes = []
    primary_threshold = None
    secondary_threshold = None
    if frontier.objective_thresholds is not None:
        primary_threshold = frontier.objective_thresholds.get(
            frontier.primary_metric, None)
        secondary_threshold = frontier.objective_thresholds.get(
            frontier.secondary_metric, None)
    absolute_metrics = frontier.absolute_metrics
    rel_x = frontier.secondary_metric not in absolute_metrics
    rel_y = frontier.primary_metric not in absolute_metrics
    if primary_threshold is not None:
        shapes.append({
            "type": "line",
            "xref": "paper",
            "x0": 0.0,
            "x1": 1.0,
            "yref": "y",
            "y0": primary_threshold,
            "y1": primary_threshold,
            "line": {
                "color": rgba(COLORS.CORAL.value),
                "width": 3
            },
        })
    if secondary_threshold is not None:
        shapes.append({
            "type": "line",
            "yref": "paper",
            "y0": 0.0,
            "y1": 1.0,
            "xref": "x",
            "x0": secondary_threshold,
            "x1": secondary_threshold,
            "line": {
                "color": rgba(COLORS.CORAL.value),
                "width": 3
            },
        })

    layout = go.Layout(
        title="Pareto Frontier",
        xaxis={
            "title": frontier.secondary_metric,
            "ticksuffix": "%" if rel_x else "",
            "zeroline": True,
        },
        yaxis={
            "title": frontier.primary_metric,
            "ticksuffix": "%" if rel_y else "",
            "zeroline": True,
        },
        hovermode="closest",
        legend={"orientation": "h"},
        width=750,
        height=500,
        margin=go.layout.Margin(pad=4, l=225, b=75, t=75),  # noqa E741
        shapes=shapes,
    )

    fig = go.Figure(data=[trace], layout=layout)
    return AxPlotConfig(data=fig, plot_type=AxPlotTypes.GENERIC)
Example #13
0
def plot_multiple_pareto_frontiers(
    frontiers: Dict[str, ParetoFrontierResults],
    CI_level: float = DEFAULT_CI_LEVEL,
    show_parameterization_on_hover: bool = True,
) -> AxPlotConfig:
    """Plot a Pareto frontier from a ParetoFrontierResults object.

    Args:
        frontiers (Dict[str, ParetoFrontierResults]): The results of
            the Pareto frontier computation.
        CI_level (float, optional): The confidence level, i.e. 0.95 (95%)
        show_parameterization_on_hover (bool, optional): If True, show the
            parameterization of the points on the frontier on hover.

    Returns:
        AEPlotConfig: The resulting Plotly plot definition.

    """
    first_frontier = list(frontiers.values())[0]
    traces = []
    for i, (method, frontier) in enumerate(frontiers.items()):
        # Check the two metrics are the same as the first frontier
        if (frontier.primary_metric != first_frontier.primary_metric or
                frontier.secondary_metric != first_frontier.secondary_metric):
            raise ValueError(
                "All frontiers should have the same pairs of metrics.")

        trace = _get_single_pareto_trace(
            frontier=frontier,
            legend_label=method,
            trace_color=DISCRETE_COLOR_SCALE[i % len(DISCRETE_COLOR_SCALE)],
            CI_level=CI_level,
            show_parameterization_on_hover=show_parameterization_on_hover,
        )

        traces.append(trace)

    shapes = []
    primary_threshold = None
    secondary_threshold = None
    if frontier.objective_thresholds is not None:
        primary_threshold = frontier.objective_thresholds.get(
            frontier.primary_metric, None)
        secondary_threshold = frontier.objective_thresholds.get(
            frontier.secondary_metric, None)
    absolute_metrics = frontier.absolute_metrics
    rel_x = frontier.secondary_metric not in absolute_metrics
    rel_y = frontier.primary_metric not in absolute_metrics
    if primary_threshold is not None:
        shapes.append({
            "type": "line",
            "xref": "paper",
            "x0": 0.0,
            "x1": 1.0,
            "yref": "y",
            "y0": primary_threshold,
            "y1": primary_threshold,
            "line": {
                "color": rgba(COLORS.CORAL.value),
                "width": 3
            },
        })
    if secondary_threshold is not None:
        shapes.append({
            "type": "line",
            "yref": "paper",
            "y0": 0.0,
            "y1": 1.0,
            "xref": "x",
            "x0": secondary_threshold,
            "x1": secondary_threshold,
            "line": {
                "color": rgba(COLORS.CORAL.value),
                "width": 3
            },
        })

    layout = go.Layout(
        title="Pareto Frontier",
        xaxis={
            "title": frontier.secondary_metric,
            "ticksuffix": "%" if rel_x else "",
            "zeroline": True,
        },
        yaxis={
            "title": frontier.primary_metric,
            "ticksuffix": "%" if rel_y else "",
            "zeroline": True,
        },
        hovermode="closest",
        legend={
            "orientation": "h",
            "yanchor": "top",
            "y": -0.20,
            "xanchor": "auto",
            "x": 0.075,
        },
        width=750,
        height=550,
        margin=go.layout.Margin(pad=4, l=225, b=125, t=75),  # noqa E741
        shapes=shapes,
    )

    fig = go.Figure(data=traces, layout=layout)
    return AxPlotConfig(data=fig, plot_type=AxPlotTypes.GENERIC)
Example #14
0
def _error_scatter_trace(
    arms: List[Union[PlotInSampleArm, PlotOutOfSampleArm]],
    y_axis_var: PlotMetric,
    x_axis_var: Optional[PlotMetric] = None,
    y_axis_label: Optional[str] = None,
    x_axis_label: Optional[str] = None,
    status_quo_arm: Optional[PlotInSampleArm] = None,
    show_CI: bool = True,
    name: str = "In-sample",
    color: Tuple[int] = COLORS.STEELBLUE.value,
    visible: bool = True,
    legendgroup: Optional[str] = None,
    showlegend: bool = True,
    hoverinfo: str = "text",
    show_arm_details_on_hover: bool = True,
    show_context: bool = False,
    arm_noun: str = "arm",
) -> Dict[str, Any]:
    """Plot scatterplot with error bars.

    Args:
        arms (List[Union[PlotInSampleArm, PlotOutOfSampleArm]]):
            a list of in-sample or out-of-sample arms.
            In-sample arms have observed data, while out-of-sample arms
            just have predicted data. As a result,
            when passing out-of-sample arms, pred must be True.
        y_axis_var: name of metric for y-axis, along with whether
            it is observed or predicted.
        x_axis_var: name of metric for x-axis,
            along with whether it is observed or predicted. If None, arm names
            are automatically used.
        y_axis_label: custom label to use for y axis.
            If None, use metric name from `y_axis_var`.
        x_axis_label: custom label to use for x axis.
            If None, use metric name from `x_axis_var` if that is not None.
        status_quo_arm: the status quo
            arm. Necessary for relative metrics.
        show_CI: if True, plot confidence intervals.
        name: name of trace. Default is "In-sample".
        color: color as rgb tuple. Default is
            (128, 177, 211), which corresponds to COLORS.STEELBLUE.
        visible: if True, trace is visible (default).
        legendgroup: group for legends.
        showlegend: if True, legend if rendered.
        hoverinfo: information to show on hover. Default is
            custom text.
        show_arm_details_on_hover: if True, display
            parameterizations of arms on hover. Default is True.
        show_context: if True and show_arm_details_on_hover,
            context will be included in the hover.
        arm_noun: noun to use instead of "arm" (e.g. group)
    """
    x, x_se, y, y_se = _error_scatter_data(
        arms=arms,
        y_axis_var=y_axis_var,
        x_axis_var=x_axis_var,
        status_quo_arm=status_quo_arm,
    )
    labels = []

    arm_names = [a.name for a in arms]

    # No relativization if no x variable.
    rel_x = x_axis_var.rel if x_axis_var else False
    rel_y = y_axis_var.rel

    for i in range(len(arm_names)):
        heading = f"<b>{arm_noun.title()} {arm_names[i]}</b><br>"
        x_lab = ("{name}: {estimate}{perc} {ci}<br>".format(
            name=x_axis_var.metric if x_axis_label is None else x_axis_label,
            estimate=(round(x[i], DECIMALS) if isinstance(
                x[i], numbers.Number) else x[i]),
            ci="" if x_se is None else _format_CI(x[i], x_se[i], rel_x),
            perc="%" if rel_x else "",
        ) if x_axis_var is not None else "")
        y_lab = "{name}: {estimate}{perc} {ci}<br>".format(
            name=y_axis_var.metric if y_axis_label is None else y_axis_label,
            estimate=(round(y[i], DECIMALS) if isinstance(
                y[i], numbers.Number) else y[i]),
            ci="" if y_se is None else _format_CI(y[i], y_se[i], rel_y),
            perc="%" if rel_y else "",
        )

        parameterization = (_format_dict(arms[i].parameters,
                                         "Parameterization")
                            if show_arm_details_on_hover else "")

        context = (
            # Expected `Dict[str, Optional[Union[bool, float, str]]]` for 1st anonymous
            # parameter to call `ax.plot.helper._format_dict` but got
            # `Optional[Dict[str, Union[float, str]]]`.
            # pyre-fixme[6]:
            _format_dict(arms[i].context_stratum, "Context")
            if show_arm_details_on_hover and show_context  # noqa W503
            and arms[i].context_stratum  # noqa W503
            else "")

        labels.append("{arm_name}<br>{xlab}{ylab}{param_blob}{context}".format(
            arm_name=heading,
            xlab=x_lab,
            ylab=y_lab,
            param_blob=parameterization,
            context=context,
        ))
        i += 1
    trace = go.Scatter(
        x=x,
        y=y,
        marker={"color": rgba(color)},
        mode="markers",
        name=name,
        text=labels,
        hoverinfo=hoverinfo,
    )

    if show_CI:
        if x_se is not None:
            trace.update(
                error_x={
                    "type": "data",
                    "array": np.multiply(x_se, Z),
                    "color": rgba(color, CI_OPACITY),
                })
        if y_se is not None:
            trace.update(
                error_y={
                    "type": "data",
                    "array": np.multiply(y_se, Z),
                    "color": rgba(color, CI_OPACITY),
                })
    if visible is not None:
        trace.update(visible=visible)
    if legendgroup is not None:
        trace.update(legendgroup=legendgroup)
    if showlegend is not None:
        trace.update(showlegend=showlegend)
    return trace
Example #15
0
def lattice_multiple_metrics(
    model: ModelBridge,
    generator_runs_dict: TNullableGeneratorRunsDict = None,
    rel: bool = True,
    show_arm_details_on_hover: bool = False,
) -> AxPlotConfig:
    """Plot raw values or predictions of combinations of two metrics for arms.

    Args:
        model: model to draw predictions from.
        generator_runs_dict: a mapping from
            generator run name to generator run.
        rel: if True, use relative effects. Default is True.
        show_arm_details_on_hover: if True, display
            parameterizations of arms on hover. Default is False.

    """
    metrics = model.metric_names
    fig = tools.make_subplots(
        rows=len(metrics),
        cols=len(metrics),
        print_grid=False,
        shared_xaxes=False,
        shared_yaxes=False,
    )

    plot_data, _, _ = get_plot_data(
        model, generator_runs_dict if generator_runs_dict is not None else {},
        metrics)
    status_quo_arm = (
        None if plot_data.status_quo_name is None
        # pyre-fixme[6]: Expected `str` for 1st param but got `Optional[str]`.
        else plot_data.in_sample.get(plot_data.status_quo_name))

    # iterate over all combinations of metrics and generate scatter traces
    for i, o1 in enumerate(metrics, start=1):
        for j, o2 in enumerate(metrics, start=1):
            if o1 != o2:
                # in-sample observed and predicted
                obs_insample_trace = _error_scatter_trace(
                    # Expected `List[Union[PlotInSampleArm,
                    # PlotOutOfSampleArm]]` for 1st anonymous parameter to call
                    # `ax.plot.scatter._error_scatter_trace` but got
                    # `List[PlotInSampleArm]`.
                    # pyre-fixme[6]:
                    list(plot_data.in_sample.values()),
                    x_axis_var=PlotMetric(o1, pred=False, rel=rel),
                    y_axis_var=PlotMetric(o2, pred=False, rel=rel),
                    status_quo_arm=status_quo_arm,
                    showlegend=(i == 1 and j == 2),
                    legendgroup="In-sample",
                    visible=False,
                    show_arm_details_on_hover=show_arm_details_on_hover,
                )
                predicted_insample_trace = _error_scatter_trace(
                    # Expected `List[Union[PlotInSampleArm,
                    # PlotOutOfSampleArm]]` for 1st anonymous parameter to call
                    # `ax.plot.scatter._error_scatter_trace` but got
                    # `List[PlotInSampleArm]`.
                    # pyre-fixme[6]:
                    list(plot_data.in_sample.values()),
                    x_axis_var=PlotMetric(o1, pred=True, rel=rel),
                    y_axis_var=PlotMetric(o2, pred=True, rel=rel),
                    status_quo_arm=status_quo_arm,
                    legendgroup="In-sample",
                    showlegend=(i == 1 and j == 2),
                    visible=True,
                    show_arm_details_on_hover=show_arm_details_on_hover,
                )
                fig.append_trace(obs_insample_trace, j, i)
                fig.append_trace(predicted_insample_trace, j, i)

                # iterate over models here
                for k, (generator_run_name, cand_arms) in enumerate(
                    (plot_data.out_of_sample or {}).items(), start=1):
                    fig.append_trace(
                        _error_scatter_trace(
                            list(cand_arms.values()),
                            x_axis_var=PlotMetric(o1, pred=True, rel=rel),
                            y_axis_var=PlotMetric(o2, pred=True, rel=rel),
                            status_quo_arm=status_quo_arm,
                            name=generator_run_name,
                            color=DISCRETE_COLOR_SCALE[k],
                            showlegend=(i == 1 and j == 2),
                            legendgroup=generator_run_name,
                            show_arm_details_on_hover=show_arm_details_on_hover,
                        ),
                        j,
                        i,
                    )
            else:
                # if diagonal is set to True, add box plots
                fig.append_trace(
                    go.Box(
                        y=[arm.y[o1] for arm in plot_data.in_sample.values()],
                        name=None,
                        marker={"color": rgba(COLORS.STEELBLUE.value)},
                        showlegend=False,
                        legendgroup="In-sample",
                        visible=False,
                        hoverinfo="none",
                    ),
                    j,
                    i,
                )
                fig.append_trace(
                    go.Box(
                        y=[
                            arm.y_hat[o1]
                            for arm in plot_data.in_sample.values()
                        ],
                        name=None,
                        marker={"color": rgba(COLORS.STEELBLUE.value)},
                        showlegend=False,
                        legendgroup="In-sample",
                        hoverinfo="none",
                    ),
                    j,
                    i,
                )

                for k, (generator_run_name, cand_arms) in enumerate(
                    (plot_data.out_of_sample or {}).items(), start=1):
                    fig.append_trace(
                        go.Box(
                            y=[arm.y_hat[o1] for arm in cand_arms.values()],
                            name=None,
                            marker={"color": rgba(DISCRETE_COLOR_SCALE[k])},
                            showlegend=False,
                            legendgroup=generator_run_name,
                            hoverinfo="none",
                        ),
                        j,
                        i,
                    )

    fig["layout"].update(
        height=800,
        width=960,
        font={"size": 10},
        hovermode="closest",
        legend={
            "orientation": "h",
            "x": 0,
            "y": 1.05,
            "xanchor": "left",
            "yanchor": "middle",
        },
        updatemenus=[
            {
                "x":
                0.35,
                "y":
                1.08,
                "xanchor":
                "left",
                "yanchor":
                "middle",
                "buttons": [
                    {
                        "args": [{
                            "error_x.width": 0,
                            "error_x.thickness": 0,
                            "error_y.width": 0,
                            "error_y.thickness": 0,
                        }],
                        "label":
                        "No",
                        "method":
                        "restyle",
                    },
                    {
                        "args": [{
                            "error_x.width": 4,
                            "error_x.thickness": 2,
                            "error_y.width": 4,
                            "error_y.thickness": 2,
                        }],
                        "label":
                        "Yes",
                        "method":
                        "restyle",
                    },
                ],
            },
            {
                "x":
                0.1,
                "y":
                1.08,
                "xanchor":
                "left",
                "yanchor":
                "middle",
                "buttons": [
                    {
                        "args": [{
                            "visible":
                            (([False, True] +
                              [True] * len(plot_data.out_of_sample or {})) *
                             (len(metrics)**2))
                        }],
                        "label":
                        "Modeled",
                        "method":
                        "restyle",
                    },
                    {
                        "args": [{
                            "visible":
                            (([True, False] +
                              [False] * len(plot_data.out_of_sample or {})) *
                             (len(metrics)**2))
                        }],
                        "label":
                        "In-sample",
                        "method":
                        "restyle",
                    },
                ],
            },
        ],
        annotations=[
            {
                "x": 0.02,
                "y": 1.1,
                "xref": "paper",
                "yref": "paper",
                "text": "Type",
                "showarrow": False,
                "yanchor": "middle",
                "xanchor": "left",
            },
            {
                "x": 0.30,
                "y": 1.1,
                "xref": "paper",
                "yref": "paper",
                "text": "Show CI",
                "showarrow": False,
                "yanchor": "middle",
                "xanchor": "left",
            },
        ],
    )

    # add metric names to axes - add to each subplot if boxplots on the
    # diagonal and axes are not shared; else, add to the leftmost y-axes
    # and bottom x-axes.
    for i, o in enumerate(metrics):
        pos_x = len(metrics) * len(metrics) - len(metrics) + i + 1
        pos_y = 1 + (len(metrics) * i)
        fig["layout"]["xaxis{}".format(pos_x)].update(title=_wrap_metric(o),
                                                      titlefont={"size": 10})
        fig["layout"]["yaxis{}".format(pos_y)].update(title=_wrap_metric(o),
                                                      titlefont={"size": 10})

    # do not put x-axis ticks for boxplots
    boxplot_xaxes = []
    for trace in fig["data"]:
        if trace["type"] == "box":
            # stores the xaxes which correspond to boxplot subplots
            # since we use xaxis1, xaxis2, etc, in plotly.py
            boxplot_xaxes.append("xaxis{}".format(trace["xaxis"][1:]))
        else:
            # clear all error bars since default is no CI
            trace["error_x"].update(width=0, thickness=0)
            trace["error_y"].update(width=0, thickness=0)
    for xaxis in boxplot_xaxes:
        fig["layout"][xaxis]["showticklabels"] = False

    return AxPlotConfig(data=fig, plot_type=AxPlotTypes.GENERIC)
Example #16
0
def scatter_plot_with_pareto_frontier_plotly(
    Y: np.ndarray,
    Y_pareto: np.ndarray,
    metric_x: str,
    metric_y: str,
    reference_point: Tuple[float, float],
    minimize: bool = True,
) -> go.Figure:
    """Plots a scatter of all points in ``Y`` for ``metric_x`` and ``metric_y``
    with a reference point and Pareto frontier from ``Y_pareto``.

    Points in the scatter are colored in a gradient representing their trial index,
    with metric_x on x-axis and metric_y on y-axis. Reference point is represented
    as a star and Pareto frontier –– as a line. The frontier connects to the reference
    point via projection lines.

    NOTE: Both metrics should have the same minimization setting, passed as `minimize`.

    Args:
        Y: Array of outcomes, of which the first two will be plotted.
        Y_pareto: Array of Pareto-optimal points, first two outcomes in which will be
            plotted.
        metric_x: Name of first outcome in ``Y``.
        metric_Y: Name of second outcome in ``Y``.
        reference_point: Reference point for ``metric_x`` and ``metric_y``.
        minimize: Whether the two metrics in the plot are being minimized or maximized.
    """
    Xs = Y[:, 0]
    Ys = Y[:, 1]

    experimental_points_scatter = go.Scatter(
        x=Xs,
        y=Ys,
        mode="markers",
        marker={
            "color": np.linspace(0, 100, int(len(Xs) * 1.05)),
            "colorscale": "magma",
            "colorbar": {
                "tickvals": [0, 50, 100],
                "ticktext": [
                    1,
                    "iteration",
                    len(Xs),
                ],
            },
        },
        name="Experimental points",
    )
    reference_point_star = go.Scatter(
        x=[reference_point[0]],
        y=[reference_point[1]],
        mode="markers",
        marker={"color": rgba(COLORS.STEELBLUE.value), "size": 25, "symbol": "star"},
    )
    extra_point_x = min(Y_pareto[:, 0]) if minimize else max(Y_pareto[:, 0])
    reference_point_line_1 = go.Scatter(
        x=[extra_point_x, reference_point[0]],
        y=[reference_point[1], reference_point[1]],
        mode="lines",
        marker={"color": rgba(COLORS.STEELBLUE.value)},
    )
    extra_point_y = min(Y_pareto[:, 1]) if minimize else max(Y_pareto[:, 1])
    reference_point_line_2 = go.Scatter(
        x=[reference_point[0], reference_point[0]],
        y=[extra_point_y, reference_point[1]],
        mode="lines",
        marker={"color": rgba(COLORS.STEELBLUE.value)},
    )
    Y_pareto_with_extra = np.concatenate(
        (
            [[extra_point_x, reference_point[1]]],
            Y_pareto,
            [[reference_point[0], extra_point_y]],
        ),
        axis=0,
    )
    pareto_step = go.Scatter(
        x=Y_pareto_with_extra[:, 0],
        y=Y_pareto_with_extra[:, 1],
        mode="lines",
        marker={"color": rgba(COLORS.STEELBLUE.value)},
    )

    Y_no_outliers = _filter_outliers(Y=Y)
    range_x = (
        extend_range(lower=min(Y_no_outliers[:, 0]), upper=reference_point[0])
        if minimize
        else extend_range(lower=reference_point[0], upper=max(Y_no_outliers[:, 0]))
    )
    range_y = (
        extend_range(lower=min(Y_no_outliers[:, 1]), upper=reference_point[1])
        if minimize
        else extend_range(lower=reference_point[1], upper=max(Y_no_outliers[:, 1]))
    )
    layout = go.Layout(
        title="Observed points with Pareto frontier",
        showlegend=False,
        xaxis={"title": metric_x, "range": range_x},
        yaxis={"title": metric_y, "range": range_y},
    )
    return go.Figure(
        layout=layout,
        data=[
            pareto_step,
            reference_point_line_1,
            reference_point_line_2,
            experimental_points_scatter,
            reference_point_star,
        ],
    )
Example #17
0
def plot_pareto_frontier(
    frontier: ParetoFrontierResults,
    CI_level: float = DEFAULT_CI_LEVEL,
    show_parameterization_on_hover: bool = True,
) -> AxPlotConfig:
    """Plot a Pareto frontier from a ParetoFrontierResults object.

    Args:
        frontier (ParetoFrontierResults): The results of the Pareto frontier
            computation.
        CI_level (float, optional): The confidence level, i.e. 0.95 (95%)
        show_parameterization_on_hover (bool, optional): If True, show the
            parameterization of the points on the frontier on hover.

    Returns:
        AEPlotConfig: The resulting Plotly plot definition.

    """
    primary_means = frontier.means[frontier.primary_metric]
    primary_sems = frontier.sems[frontier.primary_metric]
    secondary_means = frontier.means[frontier.secondary_metric]
    secondary_sems = frontier.sems[frontier.secondary_metric]
    absolute_metrics = frontier.absolute_metrics
    all_metrics = frontier.means.keys()
    if frontier.arm_names is None:
        arm_names = [
            f"Parameterization {i}" for i in range(len(frontier.param_dicts))
        ]
    else:
        arm_names = [f"Arm {name}" for name in frontier.arm_names]

    if CI_level is not None:
        Z = 0.5 * norm.ppf(1 - (1 - CI_level) / 2)
    else:
        Z = None

    primary_threshold = None
    secondary_threshold = None
    if frontier.objective_thresholds is not None:
        primary_threshold = frontier.objective_thresholds.get(
            frontier.primary_metric, None)
        secondary_threshold = frontier.objective_thresholds.get(
            frontier.secondary_metric, None)

    labels = []
    rel_x = frontier.secondary_metric not in absolute_metrics
    rel_y = frontier.primary_metric not in absolute_metrics

    for i, param_dict in enumerate(frontier.param_dicts):
        label = f"<b>{arm_names[i]}</b><br>"
        for metric in all_metrics:
            metric_lab = _make_label(
                mean=frontier.means[metric][i],
                sem=frontier.sems[metric][i],
                name=metric,
                is_relative=metric not in absolute_metrics,
                Z=Z,
            )
            label += metric_lab

        parameterization = (_format_dict(param_dict, "Parameterization")
                            if show_parameterization_on_hover else "")
        label += parameterization
        labels.append(label)

    traces = [
        go.Scatter(
            x=secondary_means,
            y=primary_means,
            error_x={
                "type": "data",
                "array": Z * np.array(secondary_sems),
                "thickness": 2,
                "color": rgba(COLORS.STEELBLUE.value, CI_OPACITY),
            },
            error_y={
                "type": "data",
                "array": Z * np.array(primary_sems),
                "thickness": 2,
                "color": rgba(COLORS.STEELBLUE.value, CI_OPACITY),
            },
            mode="markers",
            text=labels,
            hoverinfo="text",
        )
    ]

    shapes = []
    if primary_threshold is not None:
        shapes.append({
            "type": "line",
            "xref": "paper",
            "x0": 0.0,
            "x1": 1.0,
            "yref": "y",
            "y0": primary_threshold,
            "y1": primary_threshold,
            "line": {
                "color": rgba(COLORS.CORAL.value),
                "width": 3
            },
        })
    if secondary_threshold is not None:
        shapes.append({
            "type": "line",
            "yref": "paper",
            "y0": 0.0,
            "y1": 1.0,
            "xref": "x",
            "x0": secondary_threshold,
            "x1": secondary_threshold,
            "line": {
                "color": rgba(COLORS.CORAL.value),
                "width": 3
            },
        })

    layout = go.Layout(
        title="Pareto Frontier",
        xaxis={
            "title": frontier.secondary_metric,
            "ticksuffix": "%" if rel_x else "",
            "zeroline": True,
        },
        yaxis={
            "title": frontier.primary_metric,
            "ticksuffix": "%" if rel_y else "",
            "zeroline": True,
        },
        hovermode="closest",
        legend={"orientation": "h"},
        width=750,
        height=500,
        margin=go.layout.Margin(pad=4, l=225, b=75, t=75),  # noqa E741
        shapes=shapes,
    )

    fig = go.Figure(data=traces, layout=layout)
    return AxPlotConfig(data=fig, plot_type=AxPlotTypes.GENERIC)