Skip to content
forked from pydata/xarray

Commit a6bacfe

Browse files
authored
Update DataTree repr to indicate inheritance (pydata#9470)
* Update DataTree repr to indicate inheritance Fixes pydata#9463 * fix whitespace * add more repr tests, fix failure * fix failure on windows * fix repr for inherited dimensions
1 parent 9735216 commit a6bacfe

File tree

4 files changed

+223
-49
lines changed

4 files changed

+223
-49
lines changed

asv_bench/benchmarks/datatree.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ class Datatree:
66
def setup(self):
77
run1 = DataTree.from_dict({"run1": xr.Dataset({"a": 1})})
88
self.d_few = {"run1": run1}
9-
self.d_many = {f"run{i}": run1.copy() for i in range(100)}
9+
self.d_many = {f"run{i}": xr.Dataset({"a": 1}) for i in range(100)}
1010

1111
def time_from_dict_few(self):
1212
DataTree.from_dict(self.d_few)

xarray/core/formatting.py

+109-31
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@
66
import contextlib
77
import functools
88
import math
9-
from collections import defaultdict
10-
from collections.abc import Collection, Hashable, Sequence
9+
from collections import ChainMap, defaultdict
10+
from collections.abc import Collection, Hashable, Mapping, Sequence
1111
from datetime import datetime, timedelta
1212
from itertools import chain, zip_longest
1313
from reprlib import recursive_repr
1414
from textwrap import dedent
15-
from typing import TYPE_CHECKING
15+
from typing import TYPE_CHECKING, Any
1616

1717
import numpy as np
1818
import pandas as pd
@@ -29,6 +29,7 @@
2929
if TYPE_CHECKING:
3030
from xarray.core.coordinates import AbstractCoordinates
3131
from xarray.core.datatree import DataTree
32+
from xarray.core.variable import Variable
3233

3334
UNITS = ("B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
3435

@@ -318,7 +319,7 @@ def inline_variable_array_repr(var, max_width):
318319

319320
def summarize_variable(
320321
name: Hashable,
321-
var,
322+
var: Variable,
322323
col_width: int,
323324
max_width: int | None = None,
324325
is_index: bool = False,
@@ -446,6 +447,21 @@ def coords_repr(coords: AbstractCoordinates, col_width=None, max_rows=None):
446447
)
447448

448449

450+
def inherited_coords_repr(node: DataTree, col_width=None, max_rows=None):
451+
coords = _inherited_vars(node._coord_variables)
452+
if col_width is None:
453+
col_width = _calculate_col_width(coords)
454+
return _mapping_repr(
455+
coords,
456+
title="Inherited coordinates",
457+
summarizer=summarize_variable,
458+
expand_option_name="display_expand_coords",
459+
col_width=col_width,
460+
indexes=node._indexes,
461+
max_rows=max_rows,
462+
)
463+
464+
449465
def inline_index_repr(index: pd.Index, max_width=None):
450466
if hasattr(index, "_repr_inline_"):
451467
repr_ = index._repr_inline_(max_width=max_width)
@@ -498,12 +514,12 @@ def filter_nondefault_indexes(indexes, filter_indexes: bool):
498514
}
499515

500516

501-
def indexes_repr(indexes, max_rows: int | None = None) -> str:
517+
def indexes_repr(indexes, max_rows: int | None = None, title: str = "Indexes") -> str:
502518
col_width = _calculate_col_width(chain.from_iterable(indexes))
503519

504520
return _mapping_repr(
505521
indexes,
506-
"Indexes",
522+
title,
507523
summarize_index,
508524
"display_expand_indexes",
509525
col_width=col_width,
@@ -571,8 +587,10 @@ def _element_formatter(
571587
return "".join(out)
572588

573589

574-
def dim_summary_limited(obj, col_width: int, max_rows: int | None = None) -> str:
575-
elements = [f"{k}: {v}" for k, v in obj.sizes.items()]
590+
def dim_summary_limited(
591+
sizes: Mapping[Any, int], col_width: int, max_rows: int | None = None
592+
) -> str:
593+
elements = [f"{k}: {v}" for k, v in sizes.items()]
576594
return _element_formatter(elements, col_width, max_rows)
577595

578596

@@ -676,7 +694,7 @@ def array_repr(arr):
676694
data_repr = inline_variable_array_repr(arr.variable, OPTIONS["display_width"])
677695

678696
start = f"<xarray.{type(arr).__name__} {name_str}"
679-
dims = dim_summary_limited(arr, col_width=len(start) + 1, max_rows=max_rows)
697+
dims = dim_summary_limited(arr.sizes, col_width=len(start) + 1, max_rows=max_rows)
680698
nbytes_str = render_human_readable_nbytes(arr.nbytes)
681699
summary = [
682700
f"{start}({dims})> Size: {nbytes_str}",
@@ -721,7 +739,9 @@ def dataset_repr(ds):
721739
max_rows = OPTIONS["display_max_rows"]
722740

723741
dims_start = pretty_print("Dimensions:", col_width)
724-
dims_values = dim_summary_limited(ds, col_width=col_width + 1, max_rows=max_rows)
742+
dims_values = dim_summary_limited(
743+
ds.sizes, col_width=col_width + 1, max_rows=max_rows
744+
)
725745
summary.append(f"{dims_start}({dims_values})")
726746

727747
if ds.coords:
@@ -756,7 +776,9 @@ def dims_and_coords_repr(ds) -> str:
756776
max_rows = OPTIONS["display_max_rows"]
757777

758778
dims_start = pretty_print("Dimensions:", col_width)
759-
dims_values = dim_summary_limited(ds, col_width=col_width + 1, max_rows=max_rows)
779+
dims_values = dim_summary_limited(
780+
ds.sizes, col_width=col_width + 1, max_rows=max_rows
781+
)
760782
summary.append(f"{dims_start}({dims_values})")
761783

762784
if ds.coords:
@@ -1050,39 +1072,95 @@ def diff_datatree_repr(a: DataTree, b: DataTree, compat):
10501072
return "\n".join(summary)
10511073

10521074

1053-
def _single_node_repr(node: DataTree) -> str:
1054-
"""Information about this node, not including its relationships to other nodes."""
1055-
if node.has_data or node.has_attrs:
1056-
# TODO: change this to inherited=False, in order to clarify what is
1057-
# inherited? https://github.com/pydata/xarray/issues/9463
1058-
node_view = node._to_dataset_view(rebuild_dims=False, inherited=True)
1059-
ds_info = "\n" + repr(node_view)
1060-
else:
1061-
ds_info = ""
1062-
return f"Group: {node.path}{ds_info}"
1075+
def _inherited_vars(mapping: ChainMap) -> dict:
1076+
return {k: v for k, v in mapping.parents.items() if k not in mapping.maps[0]}
1077+
1078+
1079+
def _datatree_node_repr(node: DataTree, show_inherited: bool) -> str:
1080+
summary = [f"Group: {node.path}"]
1081+
1082+
col_width = _calculate_col_width(node.variables)
1083+
max_rows = OPTIONS["display_max_rows"]
1084+
1085+
inherited_coords = _inherited_vars(node._coord_variables)
1086+
1087+
# Only show dimensions if also showing a variable or coordinates section.
1088+
show_dims = (
1089+
node._node_coord_variables
1090+
or (show_inherited and inherited_coords)
1091+
or node._data_variables
1092+
)
1093+
1094+
dim_sizes = node.sizes if show_inherited else node._node_dims
1095+
1096+
if show_dims:
1097+
# Includes inherited dimensions.
1098+
dims_start = pretty_print("Dimensions:", col_width)
1099+
dims_values = dim_summary_limited(
1100+
dim_sizes, col_width=col_width + 1, max_rows=max_rows
1101+
)
1102+
summary.append(f"{dims_start}({dims_values})")
1103+
1104+
if node._node_coord_variables:
1105+
summary.append(coords_repr(node.coords, col_width=col_width, max_rows=max_rows))
1106+
1107+
if show_inherited and inherited_coords:
1108+
summary.append(
1109+
inherited_coords_repr(node, col_width=col_width, max_rows=max_rows)
1110+
)
1111+
1112+
if show_dims:
1113+
unindexed_dims_str = unindexed_dims_repr(
1114+
dim_sizes, node.coords, max_rows=max_rows
1115+
)
1116+
if unindexed_dims_str:
1117+
summary.append(unindexed_dims_str)
1118+
1119+
if node._data_variables:
1120+
summary.append(
1121+
data_vars_repr(node._data_variables, col_width=col_width, max_rows=max_rows)
1122+
)
10631123

1124+
# TODO: only show indexes defined at this node, with a separate section for
1125+
# inherited indexes (if show_inherited=True)
1126+
display_default_indexes = _get_boolean_with_default(
1127+
"display_default_indexes", False
1128+
)
1129+
xindexes = filter_nondefault_indexes(
1130+
_get_indexes_dict(node.xindexes), not display_default_indexes
1131+
)
1132+
if xindexes:
1133+
summary.append(indexes_repr(xindexes, max_rows=max_rows))
10641134

1065-
def datatree_repr(dt: DataTree):
1135+
if node.attrs:
1136+
summary.append(attrs_repr(node.attrs, max_rows=max_rows))
1137+
1138+
return "\n".join(summary)
1139+
1140+
1141+
def datatree_repr(dt: DataTree) -> str:
10661142
"""A printable representation of the structure of this entire tree."""
10671143
renderer = RenderDataTree(dt)
10681144

10691145
name_info = "" if dt.name is None else f" {dt.name!r}"
10701146
header = f"<xarray.DataTree{name_info}>"
10711147

10721148
lines = [header]
1149+
show_inherited = True
10731150
for pre, fill, node in renderer:
1074-
node_repr = _single_node_repr(node)
1151+
node_repr = _datatree_node_repr(node, show_inherited=show_inherited)
1152+
show_inherited = False # only show inherited coords on the root
1153+
1154+
raw_repr_lines = node_repr.splitlines()
10751155

1076-
node_line = f"{pre}{node_repr.splitlines()[0]}"
1156+
node_line = f"{pre}{raw_repr_lines[0]}"
10771157
lines.append(node_line)
10781158

1079-
if node.has_data or node.has_attrs:
1080-
ds_repr = node_repr.splitlines()[2:]
1081-
for line in ds_repr:
1082-
if len(node.children) > 0:
1083-
lines.append(f"{fill}{renderer.style.vertical}{line}")
1084-
else:
1085-
lines.append(f"{fill}{' ' * len(renderer.style.vertical)}{line}")
1159+
for line in raw_repr_lines[1:]:
1160+
if len(node.children) > 0:
1161+
lines.append(f"{fill}{renderer.style.vertical}{line}")
1162+
else:
1163+
lines.append(f"{fill}{' ' * len(renderer.style.vertical)}{line}")
10861164

10871165
return "\n".join(lines)
10881166

0 commit comments

Comments
 (0)