Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 7 additions & 0 deletions doc/whats-new.rst
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,13 @@ New Features
- Disable the `cfgrib` backend if the `eccodes` library is not installed (:pull:`5083`). By `Baudouin Raoult <https://github.com/b8raoult>`_.
- Added :py:meth:`DataArray.curvefit` and :py:meth:`Dataset.curvefit` for general curve fitting applications. (:issue:`4300`, :pull:`4849`)
By `Sam Levang <https://github.com/slevang>`_.
- Add options to control expand/collapse of sections in display of Dataset and
DataArray. The function :py:func:`set_options` now takes keyword aguments
``display_expand_attrs``, ``display_expand_coords``, ``display_expand_data``,
``display_expand_data_vars``, all of which can be one of ``True`` to always
expand, ``False`` to always collapse, or ``default`` to expand unless over a
pre-defined limit (:pull:`5126`).
By `Tom White <https://github.com/tomwhite>`_.

Breaking changes
~~~~~~~~~~~~~~~~
Expand Down
33 changes: 26 additions & 7 deletions xarray/core/formatting.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from pandas.errors import OutOfBoundsDatetime

from .duck_array_ops import array_equiv
from .options import OPTIONS
from .options import OPTIONS, _get_boolean_with_default
from .pycompat import dask_array_type, sparse_array_type
from .utils import is_duck_array

Expand Down Expand Up @@ -371,15 +371,19 @@ def _calculate_col_width(col_items):
return col_width


def _mapping_repr(mapping, title, summarizer, col_width=None, max_rows=None):
def _mapping_repr(
mapping, title, summarizer, expand_option_name, col_width=None, max_rows=None
):
if col_width is None:
col_width = _calculate_col_width(mapping)
if max_rows is None:
max_rows = OPTIONS["display_max_rows"]
summary = [f"{title}:"]
if mapping:
len_mapping = len(mapping)
if len_mapping > max_rows:
if not _get_boolean_with_default(expand_option_name, default=True):
summary = [f"{summary[0]} ({len_mapping})"]
elif len_mapping > max_rows:
summary = [f"{summary[0]} ({max_rows}/{len_mapping})"]
first_rows = max_rows // 2 + max_rows % 2
items = list(mapping.items())
Expand All @@ -396,20 +400,30 @@ def _mapping_repr(mapping, title, summarizer, col_width=None, max_rows=None):


data_vars_repr = functools.partial(
_mapping_repr, title="Data variables", summarizer=summarize_datavar
_mapping_repr,
title="Data variables",
summarizer=summarize_datavar,
expand_option_name="display_expand_data_vars",
)


attrs_repr = functools.partial(
_mapping_repr, title="Attributes", summarizer=summarize_attr
_mapping_repr,
title="Attributes",
summarizer=summarize_attr,
expand_option_name="display_expand_attrs",
)


def coords_repr(coords, col_width=None):
if col_width is None:
col_width = _calculate_col_width(_get_col_items(coords))
return _mapping_repr(
coords, title="Coordinates", summarizer=summarize_coord, col_width=col_width
coords,
title="Coordinates",
summarizer=summarize_coord,
expand_option_name="display_expand_coords",
col_width=col_width,
)


Expand Down Expand Up @@ -493,9 +507,14 @@ def array_repr(arr):
else:
name_str = ""

if _get_boolean_with_default("display_expand_data", default=True):
data_repr = short_data_repr(arr)
else:
data_repr = inline_variable_array_repr(arr, OPTIONS["display_width"])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this raises for

ds = xr.tutorial.open_dataset("rasm")
with xr.set_options(display_expand_data=False):
    display(ds.Tair)

i.e. with the data being a MemoryCachedArray. Should this be something like

Suggested change
data_repr = inline_variable_array_repr(arr, OPTIONS["display_width"])
data_repr = inline_variable_array_repr(arr.variable, OPTIONS["display_width"])

or should we let display_expand_data have no effect for MemoryCachedArray (the expanded repr is already really small)? The difference would be: [2029500 values with dtype=float64] for display_expand_data=True vs ... for display_expand_data=False

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for spotting @keewis .

No strong view on which of those we go ahead with — @tomwhite are you familiar with this? Otherwise @keewis do you want to make a call?

@tomwhite how would you feel about following up your first PR with a second? :)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's more helpful to always have the expanded repr, but I'm not sure how complicated that special-case would be.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for finding this @keewis. I can put together a PR - I'm just trying to find a simple test case that exposes the problem (the tests don't currently use the tutorial as far as I can tell).

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should work with any opened file, so the test case should use either open_dataset or open_dataarray (creating the MemoryCachedArray by hand would also work but might be a bit more difficult). Something like

@requires_netCDF4
def test_repr_memory_cached_collapsed(temp_path):
    arr = xr.DataArray([0, 1], dims="x")
    arr.to_netcdf(temp_path, engine="netcdf4")

    with xr.open_dataarray(temp_path) as arr, xr.set_options(display_expand_data=True):
        # check the result of repr
        ...

might work? (temp_path should be a builtin pytest fixture to create a temporary file and return the path, but I'm not sure I got the name right)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've opened #5235 with a possible fix.


summary = [
"<xarray.{} {}({})>".format(type(arr).__name__, name_str, dim_summary(arr)),
short_data_repr(arr),
data_repr,
]

if hasattr(arr, "coords"):
Expand Down
19 changes: 16 additions & 3 deletions xarray/core/formatting_html.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import pkg_resources

from .formatting import inline_variable_array_repr, short_data_repr
from .options import _get_boolean_with_default

STATIC_FILES = ("static/html/icons-svg-inline.html", "static/css/style.css")

Expand Down Expand Up @@ -164,9 +165,14 @@ def collapsible_section(
)


def _mapping_section(mapping, name, details_func, max_items_collapse, enabled=True):
def _mapping_section(
mapping, name, details_func, max_items_collapse, expand_option_name, enabled=True
):
n_items = len(mapping)
collapsed = n_items >= max_items_collapse
expanded = _get_boolean_with_default(
expand_option_name, n_items < max_items_collapse
)
collapsed = not expanded

return collapsible_section(
name,
Expand All @@ -188,7 +194,11 @@ def dim_section(obj):
def array_section(obj):
# "unique" id to expand/collapse the section
data_id = "section-" + str(uuid.uuid4())
collapsed = "checked"
collapsed = (
"checked"
if _get_boolean_with_default("display_expand_data", default=True)
else ""
)
variable = getattr(obj, "variable", obj)
preview = escape(inline_variable_array_repr(variable, max_width=70))
data_repr = short_data_repr_html(obj)
Expand All @@ -209,6 +219,7 @@ def array_section(obj):
name="Coordinates",
details_func=summarize_coords,
max_items_collapse=25,
expand_option_name="display_expand_coords",
)


Expand All @@ -217,6 +228,7 @@ def array_section(obj):
name="Data variables",
details_func=summarize_vars,
max_items_collapse=15,
expand_option_name="display_expand_data_vars",
)


Expand All @@ -225,6 +237,7 @@ def array_section(obj):
name="Attributes",
details_func=summarize_attrs,
max_items_collapse=10,
expand_option_name="display_expand_attrs",
)


Expand Down
38 changes: 35 additions & 3 deletions xarray/core/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
DISPLAY_MAX_ROWS = "display_max_rows"
DISPLAY_STYLE = "display_style"
DISPLAY_WIDTH = "display_width"
DISPLAY_EXPAND_ATTRS = "display_expand_attrs"
DISPLAY_EXPAND_COORDS = "display_expand_coords"
DISPLAY_EXPAND_DATA_VARS = "display_expand_data_vars"
DISPLAY_EXPAND_DATA = "display_expand_data"
ENABLE_CFTIMEINDEX = "enable_cftimeindex"
FILE_CACHE_MAXSIZE = "file_cache_maxsize"
KEEP_ATTRS = "keep_attrs"
Expand All @@ -19,6 +23,10 @@
DISPLAY_MAX_ROWS: 12,
DISPLAY_STYLE: "html",
DISPLAY_WIDTH: 80,
DISPLAY_EXPAND_ATTRS: "default",
DISPLAY_EXPAND_COORDS: "default",
DISPLAY_EXPAND_DATA_VARS: "default",
DISPLAY_EXPAND_DATA: "default",
ENABLE_CFTIMEINDEX: True,
FILE_CACHE_MAXSIZE: 128,
KEEP_ATTRS: "default",
Expand All @@ -38,6 +46,10 @@ def _positive_integer(value):
DISPLAY_MAX_ROWS: _positive_integer,
DISPLAY_STYLE: _DISPLAY_OPTIONS.__contains__,
DISPLAY_WIDTH: _positive_integer,
DISPLAY_EXPAND_ATTRS: lambda choice: choice in [True, False, "default"],
DISPLAY_EXPAND_COORDS: lambda choice: choice in [True, False, "default"],
DISPLAY_EXPAND_DATA_VARS: lambda choice: choice in [True, False, "default"],
DISPLAY_EXPAND_DATA: lambda choice: choice in [True, False, "default"],
ENABLE_CFTIMEINDEX: lambda value: isinstance(value, bool),
FILE_CACHE_MAXSIZE: _positive_integer,
KEEP_ATTRS: lambda choice: choice in [True, False, "default"],
Expand Down Expand Up @@ -65,19 +77,23 @@ def _warn_on_setting_enable_cftimeindex(enable_cftimeindex):
}


def _get_keep_attrs(default):
global_choice = OPTIONS["keep_attrs"]
def _get_boolean_with_default(option, default):
global_choice = OPTIONS[option]

if global_choice == "default":
return default
elif global_choice in [True, False]:
return global_choice
else:
raise ValueError(
"The global option keep_attrs must be one of True, False or 'default'."
f"The global option {option} must be one of True, False or 'default'."
)


def _get_keep_attrs(default):
return _get_boolean_with_default("keep_attrs", default)


class set_options:
"""Set options for xarray in a controlled context.

Expand Down Expand Up @@ -108,6 +124,22 @@ class set_options:
Default: ``'default'``.
- ``display_style``: display style to use in jupyter for xarray objects.
Default: ``'text'``. Other options are ``'html'``.
- ``display_expand_attrs``: whether to expand the attributes section for
display of ``DataArray`` or ``Dataset`` objects. Can be ``True`` to always
expand, ``False`` to always collapse, or ``default`` to expand unless over
a pre-defined limit. Default: ``default``.
- ``display_expand_coords``: whether to expand the coordinates section for
display of ``DataArray`` or ``Dataset`` objects. Can be ``True`` to always
expand, ``False`` to always collapse, or ``default`` to expand unless over
a pre-defined limit. Default: ``default``.
- ``display_expand_data``: whether to expand the data section for display
of ``DataArray`` objects. Can be ``True`` to always expand, ``False`` to
always collapse, or ``default`` to expand unless over a pre-defined limit.
Default: ``default``.
- ``display_expand_data_vars``: whether to expand the data variables section
for display of ``Dataset`` objects. Can be ``True`` to always
expand, ``False`` to always collapse, or ``default`` to expand unless over
a pre-defined limit. Default: ``default``.


You can use ``set_options`` either as a context manager:
Expand Down
27 changes: 27 additions & 0 deletions xarray/tests/test_formatting.py
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,17 @@ def test_array_repr(self):

assert actual == expected

with xr.set_options(display_expand_data=False):
actual = formatting.array_repr(ds[(1, 2)])
expected = dedent(
"""\
<xarray.DataArray (1, 2) (test: 1)>
0
Dimensions without coordinates: test"""
)

assert actual == expected


def test_inline_variable_array_repr_custom_repr():
class CustomArray:
Expand Down Expand Up @@ -492,3 +503,19 @@ def test__mapping_repr(display_max_rows, n_vars, n_attr):
len_summary = len(summary)
data_vars_print_size = min(display_max_rows, len_summary)
assert len_summary == data_vars_print_size

with xr.set_options(
display_expand_coords=False,
display_expand_data_vars=False,
display_expand_attrs=False,
):
actual = formatting.dataset_repr(ds)
expected = dedent(
f"""\
<xarray.Dataset>
Dimensions: (time: 2)
Coordinates: (1)
Data variables: ({n_vars})
Attributes: ({n_attr})"""
)
assert actual == expected
25 changes: 25 additions & 0 deletions xarray/tests/test_formatting_html.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,17 @@ def test_repr_of_dataarray(dataarray):
formatted.count("class='xr-section-summary-in' type='checkbox' disabled >") == 2
)

with xr.set_options(display_expand_data=False):
formatted = fh.array_repr(dataarray)
assert "dim_0" in formatted
# has an expanded data section
assert formatted.count("class='xr-array-in' type='checkbox' checked>") == 0
# coords and attrs don't have an items so they'll be be disabled and collapsed
assert (
formatted.count("class='xr-section-summary-in' type='checkbox' disabled >")
== 2
)


def test_summary_of_multiindex_coord(multiindex):
idx = multiindex.x.variable.to_index_variable()
Expand All @@ -138,6 +149,20 @@ def test_repr_of_dataset(dataset):
assert "&lt;U4" in formatted or "&gt;U4" in formatted
assert "&lt;IA&gt;" in formatted

with xr.set_options(
display_expand_coords=False,
display_expand_data_vars=False,
display_expand_attrs=False,
):
formatted = fh.dataset_repr(dataset)
# coords, attrs, and data_vars are collapsed
assert (
formatted.count("class='xr-section-summary-in' type='checkbox' checked>")
== 0
)
assert "&lt;U4" in formatted or "&gt;U4" in formatted
assert "&lt;IA&gt;" in formatted


def test_repr_text_fallback(dataset):
formatted = fh.dataset_repr(dataset)
Expand Down