Repository: Epistimio/orion

Issue:
Add option to support plotting with all evc tree trials
Currently all plots are done using `with_evc_tree=False`. This should be configurable.

Discussion:

Patch:
diff --git a/src/orion/plotting/backend_plotly.py b/src/orion/plotting/backend_plotly.py
index 25922067..20777883 100644
--- a/src/orion/plotting/backend_plotly.py
+++ b/src/orion/plotting/backend_plotly.py
@@ -20,6 +20,7 @@ from orion.core.worker.transformer import build_required_space
 
 def lpi(
     experiment,
+    with_evc_tree=True,
     model="RandomForestRegressor",
     model_kwargs=None,
     n_points=20,
@@ -33,7 +34,7 @@ def lpi(
     if model_kwargs is None:
         model_kwargs = {}
 
-    df = experiment.to_pandas()
+    df = experiment.to_pandas(with_evc_tree=with_evc_tree)
     df = df.loc[df["status"] == "completed"]
 
     if df.empty:
@@ -68,14 +69,16 @@ def lpi(
     return fig
 
 
-def parallel_coordinates(experiment, order=None, colorscale="YlOrRd", **kwargs):
+def parallel_coordinates(
+    experiment, with_evc_tree=True, order=None, colorscale="YlOrRd", **kwargs
+):
     """Plotly implementation of `orion.plotting.parallel_coordinates`"""
 
     def build_frame():
         """Builds the dataframe for the plot"""
         names = list(experiment.space.keys())
 
-        df = experiment.to_pandas()
+        df = experiment.to_pandas(with_evc_tree=with_evc_tree)
         df = df.loc[df["status"] == "completed"]
 
         if df.empty:
@@ -161,7 +164,7 @@ def parallel_coordinates(experiment, order=None, colorscale="YlOrRd", **kwargs):
     return fig
 
 
-def rankings(experiments, order_by, **kwargs):
+def rankings(experiments, with_evc_tree=True, order_by="suggested", **kwargs):
     """Plotly implementation of `orion.plotting.rankings`"""
 
     def reformat_competitions(experiments):
@@ -209,7 +212,7 @@ def rankings(experiments, order_by, **kwargs):
 
         frames = []
         for name, experiment in competition.items():
-            df = experiment.to_pandas()
+            df = experiment.to_pandas(with_evc_tree=with_evc_tree)
             df = df.loc[df["status"] == "completed"]
             df = df.sort_values(order_by)
             df = orion.analysis.regret(df)
@@ -286,6 +289,7 @@ def rankings(experiments, order_by, **kwargs):
 
 def partial_dependencies(
     experiment,
+    with_evc_tree=True,
     params=None,
     smoothing=0.85,
     n_grid_points=10,
@@ -298,7 +302,7 @@ def partial_dependencies(
 
     def build_data():
         """Builds the dataframe for the plot"""
-        df = experiment.to_pandas()
+        df = experiment.to_pandas(with_evc_tree=with_evc_tree)
 
         names = list(experiment.space.keys())
         df["params"] = df[names].apply(_format_hyperparameters, args=(names,), axis=1)
@@ -470,12 +474,14 @@ def partial_dependencies(
     return fig
 
 
-def regret(experiment, order_by, verbose_hover, **kwargs):
+def regret(
+    experiment, with_evc_tree=True, order_by="suggested", verbose_hover=True, **kwargs
+):
     """Plotly implementation of `orion.plotting.regret`"""
 
     def build_frame():
         """Builds the dataframe for the plot"""
-        df = experiment.to_pandas()
+        df = experiment.to_pandas(with_evc_tree=with_evc_tree)
 
         names = list(experiment.space.keys())
         df["params"] = df[names].apply(_format_hyperparameters, args=(names,), axis=1)
@@ -531,7 +537,7 @@ def regret(experiment, order_by, verbose_hover, **kwargs):
     return fig
 
 
-def regrets(experiments, order_by, **kwargs):
+def regrets(experiments, with_evc_tree=True, order_by="suggested", **kwargs):
     """Plotly implementation of `orion.plotting.regrets`"""
 
     compute_average = bool(
@@ -560,7 +566,7 @@ def regrets(experiments, order_by, **kwargs):
         """Builds the dataframe for the plot"""
         frames = []
         for i, experiment in enumerate(experiments):
-            df = experiment.to_pandas()
+            df = experiment.to_pandas(with_evc_tree=with_evc_tree)
             df = df.loc[df["status"] == "completed"]
             df = df.sort_values(order_by)
             df = orion.analysis.regret(df)
diff --git a/src/orion/plotting/base.py b/src/orion/plotting/base.py
index 75f31200..a9872924 100644
--- a/src/orion/plotting/base.py
+++ b/src/orion/plotting/base.py
@@ -7,6 +7,7 @@ import orion.plotting.backend_plotly as backend
 
 def lpi(
     experiment,
+    with_evc_tree=True,
     model="RandomForestRegressor",
     model_kwargs=None,
     n_points=20,
@@ -27,6 +28,10 @@ def lpi(
     experiment: ExperimentClient or Experiment
         The orion object containing the experiment data
 
+    with_evc_tree: bool, optional
+        Fetch all trials from the EVC tree.
+        Default: True
+
     model: str
         Name of the regression model to use. Can be one of
         - AdaBoostRegressor
@@ -56,6 +61,7 @@ def lpi(
     """
     return backend.lpi(
         experiment,
+        with_evc_tree=with_evc_tree,
         model=model,
         model_kwargs=model_kwargs,
         n_points=n_points,
@@ -64,7 +70,7 @@ def lpi(
     )
 
 
-def parallel_coordinates(experiment, order=None, **kwargs):
+def parallel_coordinates(experiment, with_evc_tree=True, order=None, **kwargs):
     """
     Make a Parallel Coordinates Plot to visualize the effect of the hyperparameters
     on the objective.
@@ -74,6 +80,10 @@ def parallel_coordinates(experiment, order=None, **kwargs):
     experiment: ExperimentClient or Experiment
         The orion object containing the experiment data
 
+    with_evc_tree: bool, optional
+        Fetch all trials from the EVC tree.
+        Default: True
+
     order: list of str or None
         Indicates the order of columns in the parallel coordinate plot. By default
         the columns are sorted alphabetically with the exception of the first column
@@ -93,11 +103,14 @@ def parallel_coordinates(experiment, order=None, **kwargs):
         If no experiment is provided.
 
     """
-    return backend.parallel_coordinates(experiment, order=order, **kwargs)
+    return backend.parallel_coordinates(
+        experiment, with_evc_tree=with_evc_tree, order=order, **kwargs
+    )
 
 
 def partial_dependencies(
     experiment,
+    with_evc_tree=True,
     params=None,
     smoothing=0.85,
     n_grid_points=10,
@@ -114,6 +127,10 @@ def partial_dependencies(
     experiment: ExperimentClient or Experiment
         The orion object containing the experiment data
 
+    with_evc_tree: bool, optional
+        Fetch all trials from the EVC tree.
+        Default: True
+
     params: list of str, optional
         Indicates the parameters to include in the plots. All parameters are included by default.
 
@@ -155,6 +172,7 @@ def partial_dependencies(
 
     return backend.partial_dependencies(
         experiment,
+        with_evc_tree=with_evc_tree,
         params=params,
         smoothing=smoothing,
         n_grid_points=n_grid_points,
@@ -165,7 +183,7 @@ def partial_dependencies(
     )
 
 
-def rankings(experiments, order_by="suggested", **kwargs):
+def rankings(experiments, with_evc_tree=True, order_by="suggested", **kwargs):
     """
     Make a plot to visually compare the ranking of different hyper-optimization processes.
 
@@ -183,9 +201,13 @@ def rankings(experiments, order_by="suggested", **kwargs):
 
     Parameters
     ----------
-    experiment: list or dict
+    experiments: list or dict
         List or dictionary of experiments.
 
+    with_evc_tree: bool, optional
+        Fetch all trials from the EVC tree.
+        Default: True
+
     order_by: str
         Indicates how the trials should be ordered. Acceptable options are below.
         See attributes of ``Trial`` for more details.
@@ -208,10 +230,14 @@ def rankings(experiments, order_by="suggested", **kwargs):
         If no experiment is provided or order_by is invalid.
 
     """
-    return backend.rankings(experiments, order_by, **kwargs)
+    return backend.rankings(
+        experiments, with_evc_tree=with_evc_tree, order_by=order_by, **kwargs
+    )
 
 
-def regret(experiment, order_by="suggested", verbose_hover=True, **kwargs):
+def regret(
+    experiment, with_evc_tree=True, order_by="suggested", verbose_hover=True, **kwargs
+):
     """
     Make a plot to visualize the performance of the hyper-optimization process.
 
@@ -222,6 +248,10 @@ def regret(experiment, order_by="suggested", verbose_hover=True, **kwargs):
     experiment: ExperimentClient or Experiment
         The orion object containing the experiment data
 
+    with_evc_tree: bool, optional
+        Fetch all trials from the EVC tree.
+        Default: True
+
     order_by: str
         Indicates how the trials should be ordered. Acceptable options are below.
         See attributes of ``Trial`` for more details.
@@ -247,10 +277,16 @@ def regret(experiment, order_by="suggested", verbose_hover=True, **kwargs):
         If no experiment is provided.
 
     """
-    return backend.regret(experiment, order_by, verbose_hover, **kwargs)
+    return backend.regret(
+        experiment,
+        with_evc_tree=with_evc_tree,
+        order_by=order_by,
+        verbose_hover=verbose_hover,
+        **kwargs,
+    )
 
 
-def regrets(experiments, order_by="suggested", **kwargs):
+def regrets(experiments, with_evc_tree=True, order_by="suggested", **kwargs):
     """
     Make a plot to visually compare the performance of different hyper-optimization processes.
 
@@ -266,9 +302,13 @@ def regrets(experiments, order_by="suggested", **kwargs):
 
     Parameters
     ----------
-    experiment: list or dict
+    experiments: list or dict
         List or dictionary of experiments.
 
+    with_evc_tree: bool, optional
+        Fetch all trials from the EVC tree.
+        Default: True
+
     order_by: str
         Indicates how the trials should be ordered. Acceptable options are below.
         See attributes of ``Trial`` for more details.
@@ -291,7 +331,9 @@ def regrets(experiments, order_by="suggested", **kwargs):
         If no experiment is provided or order_by is invalid.
 
     """
-    return backend.regrets(experiments, order_by, **kwargs)
+    return backend.regrets(
+        experiments, with_evc_tree=with_evc_tree, order_by=order_by, **kwargs
+    )
 
 
 PLOT_METHODS = {


Test patch:
diff --git a/tests/unittests/plotting/test_plot_accessor.py b/tests/unittests/plotting/test_plot_accessor.py
index 18f56f1a..2f5aeaba 100644
--- a/tests/unittests/plotting/test_plot_accessor.py
+++ b/tests/unittests/plotting/test_plot_accessor.py
@@ -1,9 +1,17 @@
 """Collection of tests for :mod:`orion.plotting.orion.plotting.PlotAccessor`."""
 import pytest
 
+from orion.core.worker.experiment import Experiment
 from orion.plotting.base import PlotAccessor
 from orion.testing import create_experiment
 
+SINGLE_EXPERIMENT_PLOTS = (
+    "lpi",
+    "regret",
+    "parallel_coordinates",
+    "partial_dependencies",
+)
+
 config = dict(
     name="experiment-name",
     space={"x": "uniform(0, 200)"},
@@ -72,7 +80,7 @@ def test_regret_is_default_plot():
         check_plot(plot, "regret")
 
 
-@pytest.mark.parametrize("kind", ("lpi", "regret", "parallel_coordinates"))
+@pytest.mark.parametrize("kind", SINGLE_EXPERIMENT_PLOTS)
 def test_kind(kind):
     """Tests that a plot can be created from specifying `kind` as a parameter."""
     with create_experiment(config, trial_config, ["completed"]) as (_, _, experiment):
@@ -82,7 +90,7 @@ def test_kind(kind):
         check_plot(plot, kind)
 
 
-@pytest.mark.parametrize("kind", ("lpi", "regret", "parallel_coordinates"))
+@pytest.mark.parametrize("kind", SINGLE_EXPERIMENT_PLOTS)
 def test_call_to(kind):
     """Tests instance calls to `PlotAccessor.{kind}()`"""
     with create_experiment(config, trial_config, ["completed"]) as (_, _, experiment):
@@ -92,11 +100,37 @@ def test_call_to(kind):
         check_plot(plot, kind)
 
 
-@pytest.mark.parametrize(
-    "kind", ("lpi", "regret", "parallel_coordinates", "partial_dependencies")
-)
+@pytest.mark.parametrize("kind", SINGLE_EXPERIMENT_PLOTS)
 def test_emtpy(kind):
     """Tests instance calls to `PlotAccessor.{kind}()`"""
     with create_experiment(config, trial_config, []) as (_, _, experiment):
         pa = PlotAccessor(experiment)
         getattr(pa, kind)()
+
+
+@pytest.mark.parametrize("kind", SINGLE_EXPERIMENT_PLOTS)
+def test_with_evc_tree(monkeypatch, kind):
+    """Tests that the plotly backend returns a plotly object"""
+
+    WITH_EVC_TREE = True
+
+    original_to_pandas = Experiment.to_pandas
+
+    def mock_to_pandas(self, with_evc_tree):
+        assert with_evc_tree is WITH_EVC_TREE
+        return original_to_pandas(self, with_evc_tree)
+
+    monkeypatch.setattr(
+        "orion.core.worker.experiment.Experiment.to_pandas", mock_to_pandas
+    )
+
+    with create_experiment(config, trial_config, ["completed"]) as (_, _, experiment):
+        pa = PlotAccessor(experiment)
+
+        WITH_EVC_TREE = True
+        plot = getattr(pa, kind)(with_evc_tree=WITH_EVC_TREE)
+        check_plot(plot, kind)
+
+        WITH_EVC_TREE = False
+        plot = getattr(pa, kind)(with_evc_tree=WITH_EVC_TREE)
+        check_plot(plot, kind)
diff --git a/tests/unittests/plotting/test_plotly_backend.py b/tests/unittests/plotting/test_plotly_backend.py
index 92a89c01..112c3250 100644
--- a/tests/unittests/plotting/test_plotly_backend.py
+++ b/tests/unittests/plotting/test_plotly_backend.py
@@ -172,157 +172,6 @@ def mock_train_regressor(monkeypatch, assert_model=None, assert_model_kwargs=Non
     )
 
 
-@pytest.mark.usefixtures("version_XYZ")
-class TestRankings:
-    """Tests the ``rankings()`` method provided by the plotly backend"""
-
-    def test_requires_argument(self):
-        """Tests that the experiment data are required."""
-        with pytest.raises(ValueError):
-            rankings(None)
-
-    def test_returns_plotly_object(self, monkeypatch):
-        """Tests that the plotly backend returns a plotly object"""
-        mock_experiment_with_random_to_pandas(monkeypatch)
-        with create_experiment(config, trial_config, ["completed"]) as (
-            _,
-            _,
-            experiment,
-        ):
-            plot = rankings([experiment, experiment])
-
-        assert type(plot) is plotly.graph_objects.Figure
-
-    def test_graph_layout(self, monkeypatch):
-        """Tests the layout of the plot"""
-        mock_experiment_with_random_to_pandas(monkeypatch)
-        with create_experiment(config, trial_config, ["completed"]) as (
-            _,
-            _,
-            experiment,
-        ):
-            plot = rankings([experiment])
-
-        assert_rankings_plot(plot, [f"{experiment.name}-v{experiment.version}"])
-
-    def test_list_of_experiments(self, monkeypatch):
-        """Tests the rankings with list of experiments"""
-        mock_experiment_with_random_to_pandas(monkeypatch)
-        with create_experiment(config, trial_config, ["completed"]) as (
-            _,
-            _,
-            experiment,
-        ):
-            child = orion.client.create_experiment(
-                experiment.name, branching={"branch_to": "child"}
-            )
-
-            plot = rankings([experiment, child])
-
-        # Exps are sorted alphabetically by names.
-        assert_rankings_plot(
-            plot, [f"{exp.name}-v{exp.version}" for exp in [child, experiment]]
-        )
-
-    def test_list_of_experiments_name_conflict(self, monkeypatch):
-        """Tests the rankings with list of experiments with the same name"""
-        mock_experiment_with_random_to_pandas(monkeypatch)
-        with create_experiment(config, trial_config, ["completed"]) as (
-            _,
-            _,
-            experiment,
-        ):
-            child = orion.client.create_experiment(
-                experiment.name, branching={"branch_to": experiment.name}
-            )
-            assert child.name == experiment.name
-            assert child.version == experiment.version + 1
-            plot = rankings([experiment, child])
-
-        # Exps are sorted alphabetically by names.
-        assert_rankings_plot(
-            plot, [f"{exp.name}-v{exp.version}" for exp in [experiment, child]]
-        )
-
-    def test_dict_of_experiments(self, monkeypatch):
-        """Tests the rankings with renamed experiments"""
-        mock_experiment_with_random_to_pandas(monkeypatch)
-        with create_experiment(config, trial_config, ["completed"]) as (
-            _,
-            _,
-            experiment,
-        ):
-            plot = rankings({"exp-1": experiment, "exp-2": experiment})
-
-        assert_rankings_plot(plot, ["exp-1", "exp-2"])
-
-    def test_list_of_dict_of_experiments(self, monkeypatch):
-        """Tests the rankings with avg of competitions"""
-        mock_experiment_with_random_to_pandas(monkeypatch)
-        with create_experiment(config, trial_config, ["completed"]) as (
-            _,
-            _,
-            experiment,
-        ):
-            plot = rankings(
-                [{"exp-1": experiment, "exp-2": experiment} for _ in range(10)]
-            )
-
-        assert_rankings_plot(plot, ["exp-1", "exp-2"], with_avg=True)
-
-    def test_dict_of_list_of_experiments(self, monkeypatch):
-        """Tests the rankings with avg of experiments separated in lists"""
-        mock_experiment_with_random_to_pandas(monkeypatch)
-        with create_experiment(config, trial_config, ["completed"]) as (
-            _,
-            _,
-            experiment,
-        ):
-            plot = rankings({"exp-1": [experiment] * 10, "exp-2": [experiment] * 10})
-
-        assert_rankings_plot(plot, ["exp-1", "exp-2"], with_avg=True)
-
-    def test_unbalanced_experiments(self, monkeypatch):
-        """Tests the regrets with avg of unbalanced experiments"""
-        mock_experiment_with_random_to_pandas(monkeypatch, unbalanced=True)
-        with create_experiment(config, trial_config, ["completed"]) as (
-            _,
-            _,
-            experiment,
-        ):
-            plot = rankings({"exp-1": [experiment] * 10, "exp-2": [experiment] * 10})
-
-        assert_rankings_plot(plot, ["exp-1", "exp-2"], with_avg=True, balanced=0)
-
-    def test_ignore_uncompleted_statuses(self, monkeypatch):
-        """Tests that uncompleted statuses are ignored"""
-        mock_experiment_with_random_to_pandas(
-            monkeypatch,
-            status=[
-                "completed",
-                "new",
-                "reserved",
-                "completed",
-                "broken",
-                "completed",
-                "interrupted",
-                "completed",
-            ],
-        )
-        with create_experiment(config, trial_config) as (_, _, experiment):
-            plot = rankings([experiment])
-
-        assert_rankings_plot(
-            plot, [f"{experiment.name}-v{experiment.version}"], balanced=4
-        )
-
-    def test_unsupported_order_key(self):
-        """Tests that unsupported order keys are rejected"""
-        with create_experiment(config, trial_config) as (_, _, experiment):
-            with pytest.raises(ValueError):
-                rankings([experiment], order_by="unsupported")
-
-
 @pytest.mark.usefixtures("version_XYZ")
 class TestLPI:
     """Tests the ``lpi()`` method provided by the plotly backend"""
@@ -439,6 +288,112 @@ class TestLPI:
         assert_lpi_plot(plot, dims=["x", "y[0]", "y[1]", "y[2]"])
 
 
+@pytest.mark.usefixtures("version_XYZ")
+class TestParallelCoordinates:
+    """Tests the ``parallel_coordinates()`` method provided by the plotly backend"""
+
+    def test_requires_argument(self):
+        """Tests that the experiment data are required."""
+        with pytest.raises(ValueError):
+            parallel_coordinates(None)
+
+    def test_returns_plotly_object(self):
+        """Tests that the plotly backend returns a plotly object"""
+        with create_experiment(config, trial_config, ["completed"]) as (
+            _,
+            _,
+            experiment,
+        ):
+            plot = parallel_coordinates(experiment)
+
+        assert type(plot) is plotly.graph_objects.Figure
+
+    def test_graph_layout(self):
+        """Tests the layout of the plot"""
+        with create_experiment(config, trial_config, ["completed"]) as (
+            _,
+            _,
+            experiment,
+        ):
+            plot = parallel_coordinates(experiment)
+
+        assert_parallel_coordinates_plot(plot, order=["x", "loss"])
+
+    def test_experiment_worker_as_parameter(self):
+        """Tests that ``Experiment`` is a valid parameter"""
+        with create_experiment(config, trial_config, ["completed"]) as (
+            _,
+            experiment,
+            _,
+        ):
+            plot = parallel_coordinates(experiment)
+
+        assert_parallel_coordinates_plot(plot, order=["x", "loss"])
+
+    def test_ignore_uncompleted_statuses(self):
+        """Tests that uncompleted statuses are ignored"""
+        with create_experiment(config, trial_config) as (_, _, experiment):
+            plot = parallel_coordinates(experiment)
+
+        assert_parallel_coordinates_plot(plot, order=["x", "loss"])
+
+    def test_unsupported_order_key(self):
+        """Tests that unsupported order keys are rejected"""
+        with create_experiment(config, trial_config) as (_, _, experiment):
+            with pytest.raises(ValueError):
+                parallel_coordinates(experiment, order=["unsupported"])
+
+    def test_order_columns(self):
+        """Tests that columns are sorted according to ``order``"""
+        multidim_config = copy.deepcopy(config)
+        for k in "yzutv":
+            multidim_config["space"][k] = "uniform(0, 200)"
+        with create_experiment(multidim_config, trial_config) as (_, _, experiment):
+            plot = parallel_coordinates(experiment, order="vzyx")
+
+        assert_parallel_coordinates_plot(plot, order=["v", "z", "y", "x", "loss"])
+
+    def test_multidim(self):
+        """Tests that dimensions with shape > 1 are flattened properly"""
+        multidim_config = copy.deepcopy(config)
+        multidim_config["space"]["y"] = "uniform(0, 200, shape=4)"
+        with create_experiment(multidim_config, trial_config) as (_, _, experiment):
+            plot = parallel_coordinates(experiment)
+
+        assert_parallel_coordinates_plot(
+            plot, order=["x", "y[0]", "y[1]", "y[2]", "y[3]", "loss"]
+        )
+
+    def test_fidelity(self):
+        """Tests that fidelity is set to first column by default"""
+        fidelity_config = copy.deepcopy(config)
+        fidelity_config["space"]["z"] = "fidelity(1, 200, base=3)"
+        with create_experiment(fidelity_config, trial_config) as (_, _, experiment):
+            plot = parallel_coordinates(experiment)
+
+        assert_parallel_coordinates_plot(plot, order=["z", "x", "loss"])
+
+    def test_categorical(self):
+        """Tests that categorical is supported"""
+        categorical_config = copy.deepcopy(config)
+        categorical_config["space"]["z"] = 'choices(["a", "b", "c"])'
+        with create_experiment(categorical_config, trial_config) as (_, _, experiment):
+            plot = parallel_coordinates(experiment)
+
+        assert_parallel_coordinates_plot(plot, order=["x", "z", "loss"])
+
+    def test_categorical_multidim(self):
+        """Tests that multidim categorical is supported"""
+        categorical_config = copy.deepcopy(config)
+        categorical_config["space"]["z"] = 'choices(["a", "b", "c"], shape=3)'
+        with create_experiment(categorical_config, trial_config) as (_, _, experiment):
+            plot = parallel_coordinates(experiment)
+
+        assert_parallel_coordinates_plot(
+            plot, order=["x", "z[0]", "z[1]", "z[2]", "loss"]
+        )
+
+
 @pytest.mark.usefixtures("version_XYZ")
 class TestPartialDependencies:
     """Tests the ``partial_dependencies()`` method provided by the plotly backend"""
@@ -679,82 +634,233 @@ class TestPartialDependencies:
 
         assert_partial_dependencies_plot(plot, dims=["x", "y"], n_grid_points=10)
 
-    def test_custom_n_samples(self, monkeypatch):
-        """Test changing n_samples value"""
-        mock_train_regressor(monkeypatch)
-        config = mock_space()
-        mock_experiment(monkeypatch)
+    def test_custom_n_samples(self, monkeypatch):
+        """Test changing n_samples value"""
+        mock_train_regressor(monkeypatch)
+        config = mock_space()
+        mock_experiment(monkeypatch)
+
+        PARAMS = ["x", "y"]
+        N_SAMPLES = numpy.random.randint(20, 50)
+
+        def mock_partial_dependency_grid(space, model, params, samples, n_points):
+            print(samples)
+            assert samples.shape == (N_SAMPLES, len(PARAMS))
+            return partial_dependency_grid(space, model, params, samples, n_points)
+
+        monkeypatch.setattr(
+            "orion.analysis.partial_dependency_utils.partial_dependency_grid",
+            mock_partial_dependency_grid,
+        )
+
+        with create_experiment(config, trial_config) as (_, _, experiment):
+            plot = partial_dependencies(
+                experiment,
+                n_grid_points=10,
+                model_kwargs=dict(random_state=1),
+                n_samples=N_SAMPLES,
+            )
+
+    def test_custom_colorscale(self, monkeypatch):
+        """Test changing colorscale"""
+        mock_train_regressor(monkeypatch)
+        config = mock_space()
+        mock_experiment(monkeypatch)
+
+        with create_experiment(config, trial_config) as (_, _, experiment):
+            plot = partial_dependencies(
+                experiment,
+                n_grid_points=5,
+                colorscale="Viridis",
+                model_kwargs=dict(random_state=1),
+            )
+
+        with pytest.raises(AssertionError):
+            assert_partial_dependencies_plot(
+                plot, dims=["x", "y"], n_grid_points=5, custom_colorscale=False
+            )
+
+        assert_partial_dependencies_plot(
+            plot, dims=["x", "y"], n_grid_points=5, custom_colorscale=True
+        )
+
+    def test_custom_model(self, monkeypatch):
+        """Test changing type of regression model"""
+        mock_train_regressor(monkeypatch, assert_model="BaggingRegressor")
+        config = mock_space()
+        mock_experiment(monkeypatch)
+
+        with create_experiment(config, trial_config) as (_, _, experiment):
+            plot = partial_dependencies(
+                experiment,
+                n_grid_points=5,
+                model="BaggingRegressor",
+                model_kwargs=dict(random_state=1),
+            )
+
+    def test_custom_model_kwargs(self, monkeypatch):
+        """Test changing arguments of regression model"""
+        mock_train_regressor(monkeypatch, assert_model_kwargs=dict(random_state=1))
+        config = mock_space()
+        mock_experiment(monkeypatch)
+
+        with create_experiment(config, trial_config) as (_, _, experiment):
+            plot = partial_dependencies(
+                experiment,
+                n_grid_points=5,
+                model_kwargs=dict(random_state=1),
+            )
+
+
+@pytest.mark.usefixtures("version_XYZ")
+class TestRankings:
+    """Tests the ``rankings()`` method provided by the plotly backend"""
+
+    def test_requires_argument(self):
+        """Tests that the experiment data are required."""
+        with pytest.raises(ValueError):
+            rankings(None)
+
+    def test_returns_plotly_object(self, monkeypatch):
+        """Tests that the plotly backend returns a plotly object"""
+        mock_experiment_with_random_to_pandas(monkeypatch)
+        with create_experiment(config, trial_config, ["completed"]) as (
+            _,
+            _,
+            experiment,
+        ):
+            plot = rankings([experiment, experiment])
+
+        assert type(plot) is plotly.graph_objects.Figure
+
+    def test_graph_layout(self, monkeypatch):
+        """Tests the layout of the plot"""
+        mock_experiment_with_random_to_pandas(monkeypatch)
+        with create_experiment(config, trial_config, ["completed"]) as (
+            _,
+            _,
+            experiment,
+        ):
+            plot = rankings([experiment])
+
+        assert_rankings_plot(plot, [f"{experiment.name}-v{experiment.version}"])
+
+    def test_list_of_experiments(self, monkeypatch):
+        """Tests the rankings with list of experiments"""
+        mock_experiment_with_random_to_pandas(monkeypatch)
+        with create_experiment(config, trial_config, ["completed"]) as (
+            _,
+            _,
+            experiment,
+        ):
+            child = orion.client.create_experiment(
+                experiment.name, branching={"branch_to": "child"}
+            )
+
+            plot = rankings([experiment, child])
 
-        PARAMS = ["x", "y"]
-        N_SAMPLES = numpy.random.randint(20, 50)
+        # Exps are sorted alphabetically by names.
+        assert_rankings_plot(
+            plot, [f"{exp.name}-v{exp.version}" for exp in [child, experiment]]
+        )
 
-        def mock_partial_dependency_grid(space, model, params, samples, n_points):
-            print(samples)
-            assert samples.shape == (N_SAMPLES, len(PARAMS))
-            return partial_dependency_grid(space, model, params, samples, n_points)
+    def test_list_of_experiments_name_conflict(self, monkeypatch):
+        """Tests the rankings with list of experiments with the same name"""
+        mock_experiment_with_random_to_pandas(monkeypatch)
+        with create_experiment(config, trial_config, ["completed"]) as (
+            _,
+            _,
+            experiment,
+        ):
+            child = orion.client.create_experiment(
+                experiment.name, branching={"branch_to": experiment.name}
+            )
+            assert child.name == experiment.name
+            assert child.version == experiment.version + 1
+            plot = rankings([experiment, child])
 
-        monkeypatch.setattr(
-            "orion.analysis.partial_dependency_utils.partial_dependency_grid",
-            mock_partial_dependency_grid,
+        # Exps are sorted alphabetically by names.
+        assert_rankings_plot(
+            plot, [f"{exp.name}-v{exp.version}" for exp in [experiment, child]]
         )
 
-        with create_experiment(config, trial_config) as (_, _, experiment):
-            plot = partial_dependencies(
-                experiment,
-                n_grid_points=10,
-                model_kwargs=dict(random_state=1),
-                n_samples=N_SAMPLES,
-            )
+    def test_dict_of_experiments(self, monkeypatch):
+        """Tests the rankings with renamed experiments"""
+        mock_experiment_with_random_to_pandas(monkeypatch)
+        with create_experiment(config, trial_config, ["completed"]) as (
+            _,
+            _,
+            experiment,
+        ):
+            plot = rankings({"exp-1": experiment, "exp-2": experiment})
 
-    def test_custom_colorscale(self, monkeypatch):
-        """Test changing colorscale"""
-        mock_train_regressor(monkeypatch)
-        config = mock_space()
-        mock_experiment(monkeypatch)
+        assert_rankings_plot(plot, ["exp-1", "exp-2"])
 
-        with create_experiment(config, trial_config) as (_, _, experiment):
-            plot = partial_dependencies(
-                experiment,
-                n_grid_points=5,
-                colorscale="Viridis",
-                model_kwargs=dict(random_state=1),
+    def test_list_of_dict_of_experiments(self, monkeypatch):
+        """Tests the rankings with avg of competitions"""
+        mock_experiment_with_random_to_pandas(monkeypatch)
+        with create_experiment(config, trial_config, ["completed"]) as (
+            _,
+            _,
+            experiment,
+        ):
+            plot = rankings(
+                [{"exp-1": experiment, "exp-2": experiment} for _ in range(10)]
             )
 
-        with pytest.raises(AssertionError):
-            assert_partial_dependencies_plot(
-                plot, dims=["x", "y"], n_grid_points=5, custom_colorscale=False
-            )
+        assert_rankings_plot(plot, ["exp-1", "exp-2"], with_avg=True)
 
-        assert_partial_dependencies_plot(
-            plot, dims=["x", "y"], n_grid_points=5, custom_colorscale=True
-        )
+    def test_dict_of_list_of_experiments(self, monkeypatch):
+        """Tests the rankings with avg of experiments separated in lists"""
+        mock_experiment_with_random_to_pandas(monkeypatch)
+        with create_experiment(config, trial_config, ["completed"]) as (
+            _,
+            _,
+            experiment,
+        ):
+            plot = rankings({"exp-1": [experiment] * 10, "exp-2": [experiment] * 10})
 
-    def test_custom_model(self, monkeypatch):
-        """Test changing type of regression model"""
-        mock_train_regressor(monkeypatch, assert_model="BaggingRegressor")
-        config = mock_space()
-        mock_experiment(monkeypatch)
+        assert_rankings_plot(plot, ["exp-1", "exp-2"], with_avg=True)
+
+    def test_unbalanced_experiments(self, monkeypatch):
+        """Tests the regrets with avg of unbalanced experiments"""
+        mock_experiment_with_random_to_pandas(monkeypatch, unbalanced=True)
+        with create_experiment(config, trial_config, ["completed"]) as (
+            _,
+            _,
+            experiment,
+        ):
+            plot = rankings({"exp-1": [experiment] * 10, "exp-2": [experiment] * 10})
+
+        assert_rankings_plot(plot, ["exp-1", "exp-2"], with_avg=True, balanced=0)
 
+    def test_ignore_uncompleted_statuses(self, monkeypatch):
+        """Tests that uncompleted statuses are ignored"""
+        mock_experiment_with_random_to_pandas(
+            monkeypatch,
+            status=[
+                "completed",
+                "new",
+                "reserved",
+                "completed",
+                "broken",
+                "completed",
+                "interrupted",
+                "completed",
+            ],
+        )
         with create_experiment(config, trial_config) as (_, _, experiment):
-            plot = partial_dependencies(
-                experiment,
-                n_grid_points=5,
-                model="BaggingRegressor",
-                model_kwargs=dict(random_state=1),
-            )
+            plot = rankings([experiment])
 
-    def test_custom_model_kwargs(self, monkeypatch):
-        """Test changing arguments of regression model"""
-        mock_train_regressor(monkeypatch, assert_model_kwargs=dict(random_state=1))
-        config = mock_space()
-        mock_experiment(monkeypatch)
+        assert_rankings_plot(
+            plot, [f"{experiment.name}-v{experiment.version}"], balanced=4
+        )
 
+    def test_unsupported_order_key(self):
+        """Tests that unsupported order keys are rejected"""
         with create_experiment(config, trial_config) as (_, _, experiment):
-            plot = partial_dependencies(
-                experiment,
-                n_grid_points=5,
-                model_kwargs=dict(random_state=1),
-            )
+            with pytest.raises(ValueError):
+                rankings([experiment], order_by="unsupported")
 
 
 @pytest.mark.usefixtures("version_XYZ")
@@ -948,109 +1054,3 @@ class TestRegrets:
         with create_experiment(config, trial_config) as (_, _, experiment):
             with pytest.raises(ValueError):
                 regrets([experiment], order_by="unsupported")
-
-
-@pytest.mark.usefixtures("version_XYZ")
-class TestParallelCoordinates:
-    """Tests the ``parallel_coordinates()`` method provided by the plotly backend"""
-
-    def test_requires_argument(self):
-        """Tests that the experiment data are required."""
-        with pytest.raises(ValueError):
-            parallel_coordinates(None)
-
-    def test_returns_plotly_object(self):
-        """Tests that the plotly backend returns a plotly object"""
-        with create_experiment(config, trial_config, ["completed"]) as (
-            _,
-            _,
-            experiment,
-        ):
-            plot = parallel_coordinates(experiment)
-
-        assert type(plot) is plotly.graph_objects.Figure
-
-    def test_graph_layout(self):
-        """Tests the layout of the plot"""
-        with create_experiment(config, trial_config, ["completed"]) as (
-            _,
-            _,
-            experiment,
-        ):
-            plot = parallel_coordinates(experiment)
-
-        assert_parallel_coordinates_plot(plot, order=["x", "loss"])
-
-    def test_experiment_worker_as_parameter(self):
-        """Tests that ``Experiment`` is a valid parameter"""
-        with create_experiment(config, trial_config, ["completed"]) as (
-            _,
-            experiment,
-            _,
-        ):
-            plot = parallel_coordinates(experiment)
-
-        assert_parallel_coordinates_plot(plot, order=["x", "loss"])
-
-    def test_ignore_uncompleted_statuses(self):
-        """Tests that uncompleted statuses are ignored"""
-        with create_experiment(config, trial_config) as (_, _, experiment):
-            plot = parallel_coordinates(experiment)
-
-        assert_parallel_coordinates_plot(plot, order=["x", "loss"])
-
-    def test_unsupported_order_key(self):
-        """Tests that unsupported order keys are rejected"""
-        with create_experiment(config, trial_config) as (_, _, experiment):
-            with pytest.raises(ValueError):
-                parallel_coordinates(experiment, order=["unsupported"])
-
-    def test_order_columns(self):
-        """Tests that columns are sorted according to ``order``"""
-        multidim_config = copy.deepcopy(config)
-        for k in "yzutv":
-            multidim_config["space"][k] = "uniform(0, 200)"
-        with create_experiment(multidim_config, trial_config) as (_, _, experiment):
-            plot = parallel_coordinates(experiment, order="vzyx")
-
-        assert_parallel_coordinates_plot(plot, order=["v", "z", "y", "x", "loss"])
-
-    def test_multidim(self):
-        """Tests that dimensions with shape > 1 are flattened properly"""
-        multidim_config = copy.deepcopy(config)
-        multidim_config["space"]["y"] = "uniform(0, 200, shape=4)"
-        with create_experiment(multidim_config, trial_config) as (_, _, experiment):
-            plot = parallel_coordinates(experiment)
-
-        assert_parallel_coordinates_plot(
-            plot, order=["x", "y[0]", "y[1]", "y[2]", "y[3]", "loss"]
-        )
-
-    def test_fidelity(self):
-        """Tests that fidelity is set to first column by default"""
-        fidelity_config = copy.deepcopy(config)
-        fidelity_config["space"]["z"] = "fidelity(1, 200, base=3)"
-        with create_experiment(fidelity_config, trial_config) as (_, _, experiment):
-            plot = parallel_coordinates(experiment)
-
-        assert_parallel_coordinates_plot(plot, order=["z", "x", "loss"])
-
-    def test_categorical(self):
-        """Tests that categorical is supported"""
-        categorical_config = copy.deepcopy(config)
-        categorical_config["space"]["z"] = 'choices(["a", "b", "c"])'
-        with create_experiment(categorical_config, trial_config) as (_, _, experiment):
-            plot = parallel_coordinates(experiment)
-
-        assert_parallel_coordinates_plot(plot, order=["x", "z", "loss"])
-
-    def test_categorical_multidim(self):
-        """Tests that multidim categorical is supported"""
-        categorical_config = copy.deepcopy(config)
-        categorical_config["space"]["z"] = 'choices(["a", "b", "c"], shape=3)'
-        with create_experiment(categorical_config, trial_config) as (_, _, experiment):
-            plot = parallel_coordinates(experiment)
-
-        assert_parallel_coordinates_plot(
-            plot, order=["x", "z[0]", "z[1]", "z[2]", "loss"]
-        )