Repository: iterative/dvc

Issue:
plots: support yaml
DVC supports JSON and YAML for metrics, but not for plots.

Details: https://github.com/iterative/dvc.org/pull/1382#discussion_r434967192

---
UPDATE by Jorge:

- [ ] Let's remember to update docs when this is figured out 🙂 Either in iterative/dvc.org/pull/1382 or in it's own PR on top of those changes.


Discussion:
@dmpetrov actually we do support yaml, thoguh my tests are very simple, and just uses yaml-encoded list
eg:
```
-y: 2
-y: 3
```


The original issue (mentioned by @jorgeorpinel ) is about finding metric inside dictionary. Yaml parsing is already present, mechanism for extracting array of values is used in `JSONPlotData` class, so this issue should be simply about adding `_find_data` method to `YAMLPlotData`, and associated tests.
@pared so, it this a bug? let's label it appropriately and discuss with @efiop if can fix it this sprint before release.
> original issue (mentioned by @jorgeorpinel ) is about finding metric inside dictionary

If you just convert the JSON example from https://dvc.org/doc/command-reference/plots/show#example-hierarchical-data-json to YAML and try to use it, it gives the same error.

```yaml
---
train:
- accuracy: 0.96658
  loss: 0.10757
- accuracy: 0.97641
  loss: 0.07324
- accuracy: 0.87707
  loss: 0.08136
- accuracy: 0.87402
  loss: 0.09026
- accuracy: 0.8795
  loss: 0.0764
- accuracy: 0.88038
  loss: 0.07608
- accuracy: 0.89872
  loss: 0.08455
```
> Save as `train.yaml.default`

```console
λ dvc run -n yamlplot -d train.yaml.default --plots train.yaml \
          cp train.yaml.default train.yaml
...
λ dvc plots show train.yaml
ERROR: unexpected error - 'str' object has no attribute 'keys'
```

UPDATE: If you remove the first 2 lines form the YAML file

```yaml
---
train:
```

it does generate the plot correctly. Not sure whether this is how it should work, please lmk so we can document YAML support and write some examples.

Patch:
diff --git a/dvc/repo/plots/data.py b/dvc/repo/plots/data.py
--- a/dvc/repo/plots/data.py
+++ b/dvc/repo/plots/data.py
@@ -237,6 +237,10 @@ def construct_mapping(loader, node):
 
         return yaml.load(self.content, OrderedLoader)
 
+    def _processors(self):
+        parent_processors = super()._processors()
+        return [_find_data] + parent_processors
+
 
 def _load_from_revision(repo, datafile, revision):
     from dvc.repo.tree import RepoTree


Test patch:
diff --git a/tests/func/plots/test_plots.py b/tests/func/plots/test_plots.py
--- a/tests/func/plots/test_plots.py
+++ b/tests/func/plots/test_plots.py
@@ -10,9 +10,11 @@
 from funcy import first
 
 from dvc.repo.plots.data import (
+    JSONPlotData,
     NoMetricInHistoryError,
     PlotData,
     PlotMetricTypeError,
+    YAMLPlotData,
 )
 from dvc.repo.plots.template import (
     NoDataForTemplateError,
@@ -581,3 +583,29 @@ def test_raise_on_wrong_field(tmp_dir, scm, dvc, run_copy_metrics):
 
     with pytest.raises(NoFieldInDataError):
         dvc.plots.show("metric.json", props={"y": "no_val"})
+
+
+def test_load_metric_from_dict_json(tmp_dir):
+    metric = [{"acccuracy": 1, "loss": 2}, {"accuracy": 3, "loss": 4}]
+    dmetric = {"train": metric}
+
+    plot_data = JSONPlotData("-", "revision", json.dumps(dmetric))
+
+    expected = metric
+    for d in expected:
+        d["rev"] = "revision"
+
+    assert list(map(dict, plot_data.to_datapoints())) == expected
+
+
+def test_load_metric_from_dict_yaml(tmp_dir):
+    metric = [{"acccuracy": 1, "loss": 2}, {"accuracy": 3, "loss": 4}]
+    dmetric = {"train": metric}
+
+    plot_data = YAMLPlotData("-", "revision", yaml.dump(dmetric))
+
+    expected = metric
+    for d in expected:
+        d["rev"] = "revision"
+
+    assert list(map(dict, plot_data.to_datapoints())) == expected