exp show --html: Handle fill_value.

Adds new argument `fill_value` to ParallelCoordinatesRenderer.

For scalar columns, prevent converting to categorical by explictly inserting None when value is missing (value == fill_value).

For categorical columns, replace `fill_value` with `"Missing"` and ensure it is the last item so it gets rendered on top of the column.
This commit is contained in:
David de la Iglesia Castro
2021-12-17 14:45:12 +09:00
committed by Peter Rowlands (변기호)
parent 5ee82d4000
commit 4d5be46ab4
6 changed files with 65 additions and 21 deletions
+3 -1
View File
@@ -159,7 +159,9 @@ class TabularData(MutableSequence[Sequence["CellT"]]):
index_path = write(
output_path,
renderers=[ParallelCoordinatesRenderer(self, color_by)],
renderers=[
ParallelCoordinatesRenderer(self, color_by, self._fill_value)
],
)
return index_path.as_uri()
+17 -7
View File
@@ -26,11 +26,15 @@ class ParallelCoordinatesRenderer(Renderer):
# pylint: disable=W0231
def __init__(
self, tabular_data: "TabularData", color_by: Optional[str] = None
self,
tabular_data: "TabularData",
color_by: Optional[str] = None,
fill_value: str = "",
):
self.tabular_data = tabular_data
self.color_by = color_by
self.filename = "experiments"
self.fill_value = fill_value
def partial_html(self, **kwargs):
return self.as_json()
@@ -44,15 +48,23 @@ class ParallelCoordinatesRenderer(Renderer):
trace: Dict[str, Any] = {"type": "parcoords", "dimensions": []}
for label, values in tabular_dict.items():
is_categorical = False
try:
float_values = [float(x) for x in values]
float_values = [
float(x) if x != self.fill_value else None for x in values
]
except ValueError:
is_categorical = True
unique_values = sorted(set(values))
if is_categorical:
non_missing = [x for x in values if x != self.fill_value]
unique_values = sorted(set(non_missing))
unique_values.append(self.fill_value)
dummy_values = [unique_values.index(x) for x in values]
values = [
x if x != self.fill_value else "Missing" for x in values
]
trace["dimensions"].append(
{
"label": label,
@@ -70,9 +82,7 @@ class ParallelCoordinatesRenderer(Renderer):
trace["line"] = {
"color": dummy_values if is_categorical else float_values,
"showscale": True,
"colorbar": {
"title": self.color_by
}
"colorbar": {"title": self.color_by},
}
if is_categorical:
trace["line"]["colorbar"]["tickmode"] = "array"
+7 -4
View File
@@ -636,9 +636,7 @@ def test_show_parallel_coordinates(tmp_dir, dvc, scm, mocker):
assert main(["exp", "show", "--html"]) == 0
kwargs = show_experiments.call_args[1]
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 (
@@ -651,11 +649,10 @@ def test_show_parallel_coordinates(tmp_dir, dvc, scm, mocker):
assert '"label": "metrics.yaml:bar"' not in html_text
assert (
main(["exp", "show", "--html", "--color-by", "metrics.yaml:foo"]) == 0
main(["exp", "show", "--html", "--sort-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
@@ -668,3 +665,9 @@ def test_show_parallel_coordinates(tmp_dir, dvc, scm, mocker):
assert main(["exp", "show", "--html", "--open"]) == 0
webbroser_open.assert_called()
params_data = {"foo": 1, "bar": 1, "foobar": 2}
(tmp_dir / params_file).dump(params_data)
assert main(["exp", "show", "--html"]) == 0
html_text = (tmp_dir / "dvc_plots" / "index.html").read_text()
assert '{"label": "foobar", "values": [2.0, null, null]}' in html_text
+1 -1
View File
@@ -785,7 +785,7 @@ def test_show_experiments_html(tmp_dir, mocker):
show_experiments(all_experiments, html=True)
td.dropna.assert_called_with("rows")
td.dropna.assert_called_with("rows", how="all")
render_kwargs = td.render.call_args[1]
+36 -7
View File
@@ -17,8 +17,8 @@ def expected_format(result):
def test_scalar_columns():
td = TabularData(["col-1", "col-2"])
td.extend([["0.1", "1"], ["2", "0.2"]])
td = TabularData(["col-1", "col-2", "col-3"])
td.extend([["0.1", "1", ""], ["2", "0.2", "0"]])
renderer = ParallelCoordinatesRenderer(td)
result = json.loads(renderer.as_json())
@@ -33,11 +33,15 @@ def test_scalar_columns():
"label": "col-2",
"values": [1.0, 0.2],
}
assert result["data"][0]["dimensions"][2] == {
"label": "col-3",
"values": [None, 0],
}
def test_categorical_columns():
td = TabularData(["col-1"])
td.extend([["foo"], ["bar"], ["foo"]])
td = TabularData(["col-1", "col-2"])
td.extend([["foo", ""], ["bar", "foobar"], ["foo", ""]])
renderer = ParallelCoordinatesRenderer(td)
result = json.loads(renderer.as_json())
@@ -50,6 +54,12 @@ def test_categorical_columns():
"tickvals": [1, 0, 1],
"ticktext": ["foo", "bar", "foo"],
}
assert result["data"][0]["dimensions"][1] == {
"label": "col-2",
"values": [1, 0, 1],
"tickvals": [1, 0, 1],
"ticktext": ["Missing", "foobar", "Missing"],
}
def test_mixed_columns():
@@ -84,9 +94,7 @@ def test_color_by_scalar():
assert result["data"][0]["line"] == {
"color": [0.1, 2.0],
"showscale": True,
"colorbar": {
"title": "scalar"
}
"colorbar": {"title": "scalar"},
}
@@ -125,3 +133,24 @@ def test_write_parallel_coordinates(tmp_dir):
id="plot_experiments", partial=renderer.as_json()
)
assert div in html_text
def test_fill_value():
td = TabularData(["categorical", "scalar"])
td.extend([["foo", "-"], ["-", "2"]])
renderer = ParallelCoordinatesRenderer(td, fill_value="-")
result = json.loads(renderer.as_json())
assert expected_format(result)
assert result["data"][0]["dimensions"][0] == {
"label": "categorical",
"values": [0, 1],
"tickvals": [0, 1],
"ticktext": ["foo", "Missing"],
}
assert result["data"][0]["dimensions"][1] == {
"label": "scalar",
"values": [None, 2.0],
}
+1 -1
View File
@@ -279,6 +279,6 @@ def test_to_parallel_coordinates(tmp_dir, mocker):
td.render(html=True, output_path="foo")
renderer_class.assert_called_with(td, None)
renderer_class.assert_called_with(td, None, td._fill_value)
write.assert_called_with("foo", renderers=[renderer])