From 9efbc814c739da30b7ca6ed64864693e1c0fff8d Mon Sep 17 00:00:00 2001 From: Peter Rowlands Date: Mon, 31 Aug 2020 15:52:22 +0900 Subject: [PATCH 1/3] experiments: preserve --include-... column ordering --- dvc/command/experiments.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/dvc/command/experiments.py b/dvc/command/experiments.py index ce60c030b4..4087b51b54 100644 --- a/dvc/command/experiments.py +++ b/dvc/command/experiments.py @@ -29,10 +29,10 @@ def _filter_names( f" --exclude-{label}" ) - names = {tuple(name.split(".")) for name in names} + names = [tuple(name.split(".")) for name in names] def _filter(filters, update_func): - filters = {tuple(name.split(".")) for name in filters} + filters = [tuple(name.split(".")) for name in filters] for length, groups in groupby(filters, len): for group in groups: matches = [name for name in names if name[:length] == group] @@ -41,18 +41,18 @@ def _filter(filters, update_func): raise InvalidArgumentError( f"'{name}' does not match any known {label}" ) - update_func(matches) + update_func({match: None for match in matches}) if include: - ret = set() + ret = OrderedDict() _filter(include, ret.update) else: - ret = set(names) + ret = OrderedDict({name: None for name in names}) if exclude: _filter(exclude, ret.difference_update) - return {".".join(name) for name in ret} + return [".".join(name) for name in ret] def _update_names(names, items): @@ -61,7 +61,7 @@ def _update_names(names, items): item = flatten(item) names.update(item.keys()) else: - names.add(name) + names[name] = None def _collect_names(all_experiments, **kwargs): @@ -74,19 +74,19 @@ def _collect_names(all_experiments, **kwargs): _update_names(param_names, exp.get("params", {}).items()) metric_names = _filter_names( - metric_names, + sorted(metric_names), "metrics", kwargs.get("include_metrics"), kwargs.get("exclude_metrics"), ) param_names = _filter_names( - param_names, + sorted(param_names), "params", kwargs.get("include_params"), kwargs.get("exclude_params"), ) - return sorted(metric_names), sorted(param_names) + return metric_names, param_names def _collect_rows( From 3fcda69d1ce0ab0259cbd5bf2f17cced663326d9 Mon Sep 17 00:00:00 2001 From: Peter Rowlands Date: Mon, 31 Aug 2020 17:00:28 +0900 Subject: [PATCH 2/3] experiments: add --sort-by and --sort-order for exp show --- dvc/command/experiments.py | 49 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/dvc/command/experiments.py b/dvc/command/experiments.py index 4087b51b54..3d29111dd9 100644 --- a/dvc/command/experiments.py +++ b/dvc/command/experiments.py @@ -96,7 +96,19 @@ def _collect_rows( param_names, precision=DEFAULT_PRECISION, no_timestamp=False, + sort_by=None, + sort_order=None, ): + if sort_by: + if sort_by in metric_names: + sort_type = "metrics" + elif sort_by in param_names: + sort_type = "params" + else: + raise InvalidArgumentError(f"Unknown sort column '{sort_by}'") + reverse = sort_order == "desc" + experiments = _sort_exp(experiments, sort_by, sort_type, reverse) + for i, (rev, exp) in enumerate(experiments.items()): row = [] style = None @@ -122,6 +134,30 @@ def _collect_rows( yield row, style +def _sort_exp(experiments, sort_by, typ, reverse): + if "baseline" in experiments: + ret = OrderedDict({"baseline": experiments.pop("baseline")}) + else: + ret = OrderedDict() + + def _sort(item): + _, exp = item + for fname, item in exp.get(typ, {}).items(): + if isinstance(item, dict): + item = flatten(item) + else: + item = {fname: item} + if sort_by in item: + val = item[sort_by] + break + else: + val = None + return (val is None, val) + + ret.update(sorted(experiments.items(), key=_sort, reverse=reverse)) + return ret + + def _format_time(timestamp): if timestamp is None: return "-" @@ -241,6 +277,8 @@ def run(self): include_params=self.args.include_params, exclude_params=self.args.exclude_params, no_timestamp=self.args.no_timestamp, + sort_by=self.args.sort_by, + sort_order=self.args.sort_order, ) if not self.args.no_pager: @@ -418,6 +456,17 @@ def add_parser(subparsers, parent_parser): help="Exclude the specified params from output table.", metavar="", ) + experiments_show_parser.add_argument( + "--sort-by", + help="Sort related experiments by the specified metric or param.", + metavar="", + ) + experiments_show_parser.add_argument( + "--sort-order", + help="Sort order to use with --sort-by.", + choices=("asc", "desc"), + default="asc", + ) experiments_show_parser.add_argument( "--no-timestamp", action="store_true", From e466d10aef67ec8faccfac07c953f83b08b3dd60 Mon Sep 17 00:00:00 2001 From: Peter Rowlands Date: Mon, 31 Aug 2020 17:45:47 +0900 Subject: [PATCH 3/3] remove for-else --- dvc/command/experiments.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/dvc/command/experiments.py b/dvc/command/experiments.py index 3d29111dd9..8b54ed01bf 100644 --- a/dvc/command/experiments.py +++ b/dvc/command/experiments.py @@ -149,10 +149,8 @@ def _sort(item): item = {fname: item} if sort_by in item: val = item[sort_by] - break - else: - val = None - return (val is None, val) + return (val is None, val) + return (True, None) ret.update(sorted(experiments.items(), key=_sort, reverse=reverse)) return ret