diff --git a/doc/whats-new.rst b/doc/whats-new.rst index 403227b1b6b..e22805b0342 100644 --- a/doc/whats-new.rst +++ b/doc/whats-new.rst @@ -82,6 +82,13 @@ New Features - Disable the `cfgrib` backend if the `eccodes` library is not installed (:pull:`5083`). By `Baudouin Raoult `_. - Added :py:meth:`DataArray.curvefit` and :py:meth:`Dataset.curvefit` for general curve fitting applications. (:issue:`4300`, :pull:`4849`) By `Sam Levang `_. +- 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 `_. Breaking changes ~~~~~~~~~~~~~~~~ diff --git a/xarray/core/formatting.py b/xarray/core/formatting.py index 13f38820e4a..31a1b777f43 100644 --- a/xarray/core/formatting.py +++ b/xarray/core/formatting.py @@ -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 @@ -371,7 +371,9 @@ 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: @@ -379,7 +381,9 @@ def _mapping_repr(mapping, title, summarizer, col_width=None, max_rows=None): 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()) @@ -396,12 +400,18 @@ 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", ) @@ -409,7 +419,11 @@ 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, ) @@ -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"]) + summary = [ "".format(type(arr).__name__, name_str, dim_summary(arr)), - short_data_repr(arr), + data_repr, ] if hasattr(arr, "coords"): diff --git a/xarray/core/formatting_html.py b/xarray/core/formatting_html.py index 3392aef8da3..9730a0a1745 100644 --- a/xarray/core/formatting_html.py +++ b/xarray/core/formatting_html.py @@ -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") @@ -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, @@ -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) @@ -209,6 +219,7 @@ def array_section(obj): name="Coordinates", details_func=summarize_coords, max_items_collapse=25, + expand_option_name="display_expand_coords", ) @@ -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", ) @@ -225,6 +237,7 @@ def array_section(obj): name="Attributes", details_func=summarize_attrs, max_items_collapse=10, + expand_option_name="display_expand_attrs", ) diff --git a/xarray/core/options.py b/xarray/core/options.py index 129698903c4..d53c9d5d7d9 100644 --- a/xarray/core/options.py +++ b/xarray/core/options.py @@ -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" @@ -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", @@ -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"], @@ -65,8 +77,8 @@ 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 @@ -74,10 +86,14 @@ def _get_keep_attrs(default): 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. @@ -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: diff --git a/xarray/tests/test_formatting.py b/xarray/tests/test_formatting.py index 1098587dca7..1e2a968debe 100644 --- a/xarray/tests/test_formatting.py +++ b/xarray/tests/test_formatting.py @@ -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( + """\ + + 0 + Dimensions without coordinates: test""" + ) + + assert actual == expected + def test_inline_variable_array_repr_custom_repr(): class CustomArray: @@ -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"""\ + + Dimensions: (time: 2) + Coordinates: (1) + Data variables: ({n_vars}) + Attributes: ({n_attr})""" + ) + assert actual == expected diff --git a/xarray/tests/test_formatting_html.py b/xarray/tests/test_formatting_html.py index 9a210ad6fa3..4cf4509c7eb 100644 --- a/xarray/tests/test_formatting_html.py +++ b/xarray/tests/test_formatting_html.py @@ -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() @@ -138,6 +149,20 @@ def test_repr_of_dataset(dataset): assert "<U4" in formatted or ">U4" in formatted assert "<IA>" 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 "<U4" in formatted or ">U4" in formatted + assert "<IA>" in formatted + def test_repr_text_fallback(dataset): formatted = fh.dataset_repr(dataset)