Skip to content

Commit

Permalink
exp show: Add parallel coordinates plot.
Browse files Browse the repository at this point in the history
Uses `TabularData.to_parallel_coordinates`.
Adds new arguments: `html`, `color-by`, `out`, `open`

Closes #4455
  • Loading branch information
daavoo committed Nov 16, 2021
1 parent 35d5a33 commit d8c77da
Show file tree
Hide file tree
Showing 3 changed files with 175 additions and 0 deletions.
68 changes: 68 additions & 0 deletions dvc/command/experiments.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from collections import Counter, OrderedDict, defaultdict
from datetime import date, datetime
from fnmatch import fnmatch
from pathlib import Path
from typing import TYPE_CHECKING, Dict, Iterable, Optional

from funcy import compact, lmap
Expand Down Expand Up @@ -387,6 +388,7 @@ def show_experiments(
no_timestamp=False,
csv=False,
markdown=False,
html=False,
**kwargs,
):
from funcy.seqs import flatten as flatten_list
Expand Down Expand Up @@ -474,6 +476,18 @@ def show_experiments(
if kwargs.get("only_changed", False):
td.drop_duplicates("cols")

extra_render_args = {}
if html:
td.dropna("rows")
td.column("Experiment")[:] = [
# remove tree characters
str(x).encode("ascii", "ignore").strip().decode()
for x in td.column("Experiment")
]
rel = kwargs.get("out") or "dvc_plots"
extra_render_args["output_path"] = (Path.cwd() / rel).resolve()
extra_render_args["color_by"] = kwargs.get("color_by", "Experiment")

td.render(
pager=pager,
borders=True,
Expand All @@ -482,8 +496,26 @@ def show_experiments(
row_styles=row_styles,
csv=csv,
markdown=markdown,
html=html,
**extra_render_args,
)

if html and kwargs.get("open"):
import webbrowser
from platform import uname

if "Microsoft" in uname().release:
url = Path(rel) / "index.html"
else:
url = Path(extra_render_args["output_path"]) / "index.html"
url = url.as_uri()

opened = webbrowser.open(url)

if not opened:
ui.error_write("Failed to open. Please try opening it manually.")
return 1


def _normalize_headers(names, count):
return [
Expand Down Expand Up @@ -525,6 +557,10 @@ def run(self):
fill_value = "" if self.args.csv else FILL_VALUE
iso = True if self.args.csv else False

if self.args.html:
self.args.only_changed = True
self.args.no_timestamp = True

show_experiments(
all_experiments,
include_metrics=self.args.include_metrics,
Expand All @@ -541,6 +577,10 @@ def run(self):
csv=self.args.csv,
markdown=self.args.markdown,
only_changed=self.args.only_changed,
html=self.args.html,
color_by=self.args.color_by,
out=self.args.out,
open=self.args.open,
)
return 0

Expand Down Expand Up @@ -1045,6 +1085,34 @@ def add_parser(subparsers, parent_parser):
"across the selected experiments."
),
)
experiments_show_parser.add_argument(
"--html",
action="store_true",
default=False,
help="Generate a parallel coordinates plot from the tabulated output.",
)
experiments_show_parser.add_argument(
"--color-by",
default="Experiment",
help=(
"Use the specified metric or param as color in "
"the parallel coordinates plot."
),
metavar="<metric/param>",
)
experiments_show_parser.add_argument(
"-o",
"--out",
default=None,
help="Destination path to save the parallel coordinates plot to",
metavar="<path>",
).complete = completion.DIR
experiments_show_parser.add_argument(
"--open",
action="store_true",
default=False,
help="Open the parallel coordinates plot directly in the browser.",
)
experiments_show_parser.set_defaults(func=CmdExperimentsShow)

EXPERIMENTS_APPLY_HELP = (
Expand Down
73 changes: 73 additions & 0 deletions tests/func/experiments/test_show.py
Original file line number Diff line number Diff line change
Expand Up @@ -582,3 +582,76 @@ def test_show_only_changed(tmp_dir, dvc, scm, capsys):
cap = capsys.readouterr()

assert "bar" not in cap.out


def test_show_parallel_coordinates(tmp_dir, dvc, scm, mocker):
from dvc.command import experiments

webbroser_open = mocker.patch("webbrowser.open")
show_experiments = mocker.spy(experiments, "show_experiments")

tmp_dir.gen("copy.py", COPY_SCRIPT)
params_file = tmp_dir / "params.yaml"
params_data = {
"foo": 1,
"bar": 1,
}
(tmp_dir / params_file).dump(params_data)

dvc.run(
cmd="python copy.py params.yaml metrics.yaml",
metrics_no_cache=["metrics.yaml"],
params=["foo", "bar"],
name="copy-file",
deps=["copy.py"],
)
scm.add(
[
"dvc.yaml",
"dvc.lock",
"copy.py",
"params.yaml",
"metrics.yaml",
".gitignore",
]
)
scm.commit("init")

dvc.experiments.run(params=["foo=2"])

assert main(["exp", "show", "--html"]) == 0
kwargs = show_experiments.call_args[1]

assert kwargs["only_changed"]
assert kwargs["no_timestamp"]
assert kwargs["color_by"] == "Experiment"
html_text = (tmp_dir / "dvc_plots" / "index.html").read_text()

assert all(rev in html_text for rev in ["workspace", "master", "[exp-"])

assert (
'{"label": "metrics.yaml:foo", "values": [2.0, 1.0, 2.0]}' in html_text
)
assert (
'{"label": "params.yaml:foo", "values": [2.0, 1.0, 2.0]}' in html_text
)
assert '"line": {"color": [0, 1, 2]' in html_text

assert (
main(["exp", "show", "--html", "--color-by", "metrics.yaml:foo"]) == 0
)
kwargs = show_experiments.call_args[1]

assert kwargs["color_by"] == "metrics.yaml:foo"
html_text = (tmp_dir / "dvc_plots" / "index.html").read_text()
assert '"line": {"color": [2.0, 1.0, 2.0]' in html_text

assert main(["exp", "show", "--html", "--out", "experiments"]) == 0
kwargs = show_experiments.call_args[1]

assert kwargs["out"] == "experiments"
assert (tmp_dir / "experiments" / "index.html").exists()

assert main(["exp", "show", "--html", "--open"]) == 0

webbroser_open.assert_called()
34 changes: 34 additions & 0 deletions tests/unit/command/test_experiments.py
Original file line number Diff line number Diff line change
Expand Up @@ -613,3 +613,37 @@ def test_experiments_init_config(dvc, mocker):
"live": "dvclive",
}
assert m.call_args[1]["overrides"] == {"cmd": "cmd"}


def test_show_experiments_html(tmp_dir, mocker):
all_experiments = {
"workspace": {
"baseline": {
"data": {
"timestamp": None,
"params": {"params.yaml": {"data": {"foo": 1}}},
"queued": False,
"running": False,
"executor": None,
"metrics": {
"scores.json": {"data": {"bar": 0.9544670443829399}}
},
}
}
},
}
experiments_table = mocker.patch(
"dvc.command.experiments.experiments_table"
)
td = experiments_table.return_value

show_experiments(all_experiments, html=True)

td.dropna.assert_called_with("rows")

render_kwargs = td.render.call_args[1]

for arg in ["html", "output_path", "color_by"]:
assert arg in render_kwargs
assert render_kwargs["output_path"] == tmp_dir / "dvc_plots"
assert render_kwargs["color_by"] == "Experiment"

0 comments on commit d8c77da

Please sign in to comment.