Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

join together duplicate entries in the text repr #7225

Merged
merged 27 commits into from
Jul 20, 2023
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
bff2411
group the variable names of common indexes
keewis Oct 19, 2022
ae74cc6
Merge branch 'main' into indexes-repr-join-text
keewis Oct 26, 2022
e29aeb9
implement option 1: separate with just newlines
keewis Oct 26, 2022
9b90f8b
implement option 2: list-like presentation of the indexes
keewis Oct 26, 2022
2cec070
implement option 3: light box components in front of the coord names
keewis Oct 26, 2022
492ab47
implement option 4: light box components after the coord names
keewis Oct 26, 2022
56b621f
fix the repr of `Indexes`
keewis Oct 26, 2022
fa70fc5
only add combine markers for xarray indexes
keewis Oct 26, 2022
08b2504
select option 3
keewis Oct 26, 2022
6270956
improve typing
keewis Oct 26, 2022
e3341ac
whats-new.rst
keewis Oct 26, 2022
7ba0992
Merge branch 'main' into indexes-repr-join-text
keewis Jul 13, 2023
4c212d2
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 13, 2023
7936c55
simplify the visual hint construction
keewis Jul 13, 2023
bc1d61f
improve the existing index repr test
keewis Jul 13, 2023
e6e5313
check that the hints are constructed correctly
keewis Jul 13, 2023
66689ea
convert the names to tuples
keewis Jul 13, 2023
7e82e42
revert the typing of `index`
keewis Jul 13, 2023
ce8c129
more conversion to tuple
keewis Jul 13, 2023
6d7cf71
type hint the instance variable
keewis Jul 13, 2023
f2c6683
whats-new entry
keewis Jul 13, 2023
0d20dfe
Merge branch 'main' into indexes-repr-join-text
keewis Jul 14, 2023
f62c16f
don't type-check the monkeypatching
keewis Jul 14, 2023
279f948
adjust the typing, again
keewis Jul 14, 2023
e052283
use a subclass instead of monkeypatching and type-ignores
keewis Jul 18, 2023
3f18888
Merge branch 'main' into indexes-repr-join-text
keewis Jul 18, 2023
b07b337
Merge branch 'main' into indexes-repr-join-text
dcherian Jul 19, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion doc/whats-new.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ v2023.07.1 (unreleased)

New Features
~~~~~~~~~~~~

- Visually group together coordinates with the same indexes in the index section of the text repr (:pull:`7225`).
By `Justus Magin <https://github.com/keewis>`_.

Breaking changes
~~~~~~~~~~~~~~~~
Expand Down
59 changes: 40 additions & 19 deletions xarray/core/formatting.py
Original file line number Diff line number Diff line change
Expand Up @@ -424,21 +424,37 @@ def inline_index_repr(index, max_width=None):


def summarize_index(
name: Hashable, index, col_width: int, max_width: int | None = None
):
names: tuple[Hashable, ...],
index,
col_width: int,
max_width: int | None = None,
) -> str:
if max_width is None:
max_width = OPTIONS["display_width"]

preformatted = pretty_print(f" {name} ", col_width)
def prefixes(length: int) -> list[str]:
if length in (0, 1):
return [" "]

return ["┌"] + ["│"] * max(length - 2, 0) + ["└"]

index_width = max_width - len(preformatted)
preformatted = [
pretty_print(f" {prefix} {name}", col_width)
for prefix, name in zip(prefixes(len(names)), names)
]

head, *tail = preformatted
index_width = max_width - len(head)
repr_ = inline_index_repr(index, max_width=index_width)
return preformatted + repr_
return "\n".join([head + repr_] + [line.rstrip() for line in tail])


def nondefault_indexes(indexes):
def filter_nondefault_indexes(indexes, filter_indexes: bool):
from xarray.core.indexes import PandasIndex, PandasMultiIndex

if not filter_indexes:
return indexes

default_indexes = (PandasIndex, PandasMultiIndex)

return {
Expand All @@ -448,7 +464,9 @@ def nondefault_indexes(indexes):
}


def indexes_repr(indexes, col_width=None, max_rows=None):
def indexes_repr(indexes, max_rows: int | None = None) -> str:
col_width = _calculate_col_width(chain.from_iterable(indexes))

return _mapping_repr(
indexes,
"Indexes",
Expand Down Expand Up @@ -599,6 +617,12 @@ def short_data_repr(array):
return f"[{array.size} values with dtype={array.dtype}]"


def _get_indexes_dict(indexes):
return {
tuple(index_vars.keys()): idx for idx, index_vars in indexes.group_by_index()
}


@recursive_repr("<recursive array>")
def array_repr(arr):
from xarray.core.variable import Variable
Expand Down Expand Up @@ -643,15 +667,13 @@ def array_repr(arr):
display_default_indexes = _get_boolean_with_default(
"display_default_indexes", False
)
if display_default_indexes:
xindexes = arr.xindexes
else:
xindexes = nondefault_indexes(arr.xindexes)

xindexes = filter_nondefault_indexes(
_get_indexes_dict(arr.xindexes), not display_default_indexes
)

if xindexes:
summary.append(
indexes_repr(xindexes, col_width=col_width, max_rows=max_rows)
)
summary.append(indexes_repr(xindexes, max_rows=max_rows))

if arr.attrs:
summary.append(attrs_repr(arr.attrs, max_rows=max_rows))
Expand Down Expand Up @@ -682,12 +704,11 @@ def dataset_repr(ds):
display_default_indexes = _get_boolean_with_default(
"display_default_indexes", False
)
if display_default_indexes:
xindexes = ds.xindexes
else:
xindexes = nondefault_indexes(ds.xindexes)
xindexes = filter_nondefault_indexes(
_get_indexes_dict(ds.xindexes), not display_default_indexes
)
if xindexes:
summary.append(indexes_repr(xindexes, col_width=col_width, max_rows=max_rows))
summary.append(indexes_repr(xindexes, max_rows=max_rows))

if ds.attrs:
summary.append(attrs_repr(ds.attrs, max_rows=max_rows))
Expand Down
3 changes: 2 additions & 1 deletion xarray/core/indexes.py
Original file line number Diff line number Diff line change
Expand Up @@ -1338,7 +1338,8 @@ def __getitem__(self, key) -> T_PandasOrXarrayIndex:
return self._indexes[key]

def __repr__(self):
return formatting.indexes_repr(self)
indexes = formatting._get_indexes_dict(self)
return formatting.indexes_repr(indexes)


def default_indexes(
Expand Down
57 changes: 47 additions & 10 deletions xarray/tests/test_formatting.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,31 +218,68 @@ def test_attribute_repr(self) -> None:
assert "\n" not in newlines
assert "\t" not in tabs

def test_index_repr(self):
def test_index_repr(self) -> None:
from xarray.core.indexes import Index

class CustomIndex(Index):
def __init__(self, names):
names: tuple[str, ...]

def __init__(self, names: tuple[str, ...]):
self.names = names

def __repr__(self):
return f"CustomIndex(coords={self.names})"

coord_names = ["x", "y"]
coord_names = ("x", "y")
index = CustomIndex(coord_names)
name = "x"
names = ("x",)

normal = formatting.summarize_index(name, index, col_width=20)
assert name in normal
normal = formatting.summarize_index(names, index, col_width=20)
assert names[0] in normal
assert len(normal.splitlines()) == len(names)
assert "CustomIndex" in normal

CustomIndex._repr_inline_ = (
lambda self, max_width: f"CustomIndex[{', '.join(self.names)}]"
CustomIndex._repr_inline_ = ( # type: ignore
lambda self, max_width: f"CustomIndex[{', '.join(self.names)}]" # type: ignore
keewis marked this conversation as resolved.
Show resolved Hide resolved
)
inline = formatting.summarize_index(name, index, col_width=20)
assert name in inline
inline = formatting.summarize_index(names, index, col_width=20)
assert names[0] in inline
assert index._repr_inline_(max_width=40) in inline

@pytest.mark.parametrize(
"names",
(
("x",),
("x", "y"),
("x", "y", "z"),
("x", "y", "z", "a"),
),
)
def test_index_repr_grouping(self, names) -> None:
from xarray.core.indexes import Index

class CustomIndex(Index):
def __init__(self, names):
self.names = names

def __repr__(self):
return f"CustomIndex(coords={self.names})"

index = CustomIndex(names)

normal = formatting.summarize_index(names, index, col_width=20)
assert all(name in normal for name in names)
assert len(normal.splitlines()) == len(names)
assert "CustomIndex" in normal

hint_chars = [line[2] for line in normal.splitlines()]

if len(names) <= 1:
assert hint_chars == [" "]
else:
assert hint_chars[0] == "┌" and hint_chars[-1] == "└"
assert len(names) == 2 or hint_chars[1:-1] == ["│"] * (len(names) - 2)

def test_diff_array_repr(self) -> None:
da_a = xr.DataArray(
np.array([[1, 2, 3], [4, 5, 6]], dtype="int64"),
Expand Down
Loading