From 8f2a1dcc96ff2a5bb80d0c2b4f2df2a35cbdb42a Mon Sep 17 00:00:00 2001 From: Michael Niklas Date: Wed, 9 Nov 2022 21:22:40 +0100 Subject: [PATCH 1/8] import nc_time_axis when needed --- xarray/plot/utils.py | 57 ++++++++++++++++++++------------------- xarray/tests/test_plot.py | 18 ++++++++++++- 2 files changed, 46 insertions(+), 29 deletions(-) diff --git a/xarray/plot/utils.py b/xarray/plot/utils.py index 31daff58b55..47750e7e6c4 100644 --- a/xarray/plot/utils.py +++ b/xarray/plot/utils.py @@ -609,8 +609,8 @@ def _resolve_intervals_1dplot( remove_drawstyle = False # Convert intervals to double points - x_is_interval = _valid_other_type(xval, [pd.Interval]) - y_is_interval = _valid_other_type(yval, [pd.Interval]) + x_is_interval = _valid_other_type(xval, pd.Interval) + y_is_interval = _valid_other_type(yval, pd.Interval) if x_is_interval and y_is_interval: raise TypeError("Can't step plot intervals against intervals.") elif x_is_interval: @@ -628,10 +628,10 @@ def _resolve_intervals_1dplot( else: # Convert intervals to mid points and adjust labels - if _valid_other_type(xval, [pd.Interval]): + if _valid_other_type(xval, pd.Interval): xval = _interval_to_mid_points(xval) x_suffix = "_center" - if _valid_other_type(yval, [pd.Interval]): + if _valid_other_type(yval, pd.Interval): yval = _interval_to_mid_points(yval) y_suffix = "_center" @@ -646,7 +646,7 @@ def _resolve_intervals_2dplot(val, func_name): increases length by 1. """ label_extra = "" - if _valid_other_type(val, [pd.Interval]): + if _valid_other_type(val, pd.Interval): if func_name == "pcolormesh": val = _interval_to_bound_points(val) else: @@ -656,11 +656,11 @@ def _resolve_intervals_2dplot(val, func_name): return val, label_extra -def _valid_other_type(x, types): +def _valid_other_type(x: object, types: type[object] | tuple[type[object]]) -> bool: """ Do all elements of x have a type from types? """ - return all(any(isinstance(el, t) for t in types) for el in np.ravel(x)) + return all(isinstance(el, types) for el in np.ravel(x)) def _valid_numpy_subdtype(x, numpy_types): @@ -675,47 +675,48 @@ def _valid_numpy_subdtype(x, numpy_types): return any(np.issubdtype(x.dtype, t) for t in numpy_types) -def _ensure_plottable(*args): +def _ensure_plottable(*args) -> None: """ Raise exception if there is anything in args that can't be plotted on an axis by matplotlib. """ - numpy_types = [ + numpy_types = ( np.floating, np.integer, np.timedelta64, np.datetime64, np.bool_, np.str_, - ] - other_types = [datetime] + ) + other_types = (datetime,) if cftime is not None: - cftime_datetime_types = [cftime.datetime] - other_types = other_types + cftime_datetime_types + cftime_datetime_types = (cftime.datetime,) + other_types += cftime_datetime_types else: - cftime_datetime_types = [] + cftime_datetime_types = () for x in args: if not ( - _valid_numpy_subdtype(np.array(x), numpy_types) - or _valid_other_type(np.array(x), other_types) + _valid_numpy_subdtype(np.asarray(x), numpy_types) + or _valid_other_type(np.asarray(x), other_types) ): raise TypeError( "Plotting requires coordinates to be numeric, boolean, " "or dates of type numpy.datetime64, " "datetime.datetime, cftime.datetime or " - f"pandas.Interval. Received data of type {np.array(x).dtype} instead." - ) - if ( - _valid_other_type(np.array(x), cftime_datetime_types) - and not nc_time_axis_available - ): - raise ImportError( - "Plotting of arrays of cftime.datetime " - "objects or arrays indexed by " - "cftime.datetime objects requires the " - "optional `nc-time-axis` (v1.2.0 or later) " - "package." + f"pandas.Interval. Received data of type {np.asarray(x).dtype} instead." ) + if _valid_other_type(np.asarray(x), cftime_datetime_types): + if nc_time_axis_available: + # needs to be imported or it will raise an error + import nc_time_axis # noqa: F401 + else: + raise ImportError( + "Plotting of arrays of cftime.datetime " + "objects or arrays indexed by " + "cftime.datetime objects requires the " + "optional `nc-time-axis` (v1.2.0 or later) " + "package." + ) def _is_numeric(arr): diff --git a/xarray/tests/test_plot.py b/xarray/tests/test_plot.py index 01f616f92ba..cb0746179a0 100644 --- a/xarray/tests/test_plot.py +++ b/xarray/tests/test_plot.py @@ -3206,7 +3206,7 @@ def test_plot_empty_raises(val: list | float, method: str) -> None: @requires_matplotlib -def test_facetgrid_axes_raises_deprecation_warning(): +def test_facetgrid_axes_raises_deprecation_warning() -> None: with pytest.warns( DeprecationWarning, match=( @@ -3218,3 +3218,19 @@ def test_facetgrid_axes_raises_deprecation_warning(): ds = xr.tutorial.scatter_example_dataset() g = ds.plot.scatter(x="A", y="B", col="x") g.axes + + +@requires_matplotlib +@requires_nc_time_axis +def test_plot_nc_time() -> None: + da = xr.DataArray( + range(10), + dims="time", + coords={ + "time": xr.cftime_range( + "1900-01-01", periods=10, calendar="noleap", freq="D" + ) + }, + ) + with figure_context(): + da.plot() From 0d183ff125dce1ec2c6fcbd5487745e62bc1739b Mon Sep 17 00:00:00 2001 From: Michael Niklas Date: Wed, 9 Nov 2022 21:25:54 +0100 Subject: [PATCH 2/8] add to whats-new --- doc/whats-new.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/whats-new.rst b/doc/whats-new.rst index 690b1336e31..97936dff700 100644 --- a/doc/whats-new.rst +++ b/doc/whats-new.rst @@ -35,6 +35,8 @@ Deprecations Bug fixes ~~~~~~~~~ +- Import ``nc_time_axis`` when needed (:issue:`7275`, :pull:`7276`). + By `Michael Niklas `_. Documentation ~~~~~~~~~~~~~ From 32ed1a36b993b45892780f1e983c77e0dc535269 Mon Sep 17 00:00:00 2001 From: Michael Niklas Date: Wed, 9 Nov 2022 21:31:35 +0100 Subject: [PATCH 3/8] add nc_time_axis to lazy tests --- xarray/tests/test_plugins.py | 1 + 1 file changed, 1 insertion(+) diff --git a/xarray/tests/test_plugins.py b/xarray/tests/test_plugins.py index d44973e25e4..8029eb3f228 100644 --- a/xarray/tests/test_plugins.py +++ b/xarray/tests/test_plugins.py @@ -203,6 +203,7 @@ def test_lazy_import() -> None: "scipy", "zarr", "matplotlib", + "nc_time_axis", "flox", # "dask", # TODO: backends.locks is not lazy yet :( "dask.array", From a3ff3ade94b7c815f90fc4938d125d69b8b06c58 Mon Sep 17 00:00:00 2001 From: Michael Niklas Date: Wed, 9 Nov 2022 21:46:39 +0100 Subject: [PATCH 4/8] fix mypy --- xarray/plot/utils.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/xarray/plot/utils.py b/xarray/plot/utils.py index 47750e7e6c4..7943452e89f 100644 --- a/xarray/plot/utils.py +++ b/xarray/plot/utils.py @@ -656,7 +656,9 @@ def _resolve_intervals_2dplot(val, func_name): return val, label_extra -def _valid_other_type(x: object, types: type[object] | tuple[type[object]]) -> bool: +def _valid_other_type( + x: ArrayLike, types: type[object] | tuple[type[object], ...] +) -> bool: """ Do all elements of x have a type from types? """ @@ -680,7 +682,7 @@ def _ensure_plottable(*args) -> None: Raise exception if there is anything in args that can't be plotted on an axis by matplotlib. """ - numpy_types = ( + numpy_types: tuple[type[object], ...] = ( np.floating, np.integer, np.timedelta64, @@ -688,7 +690,7 @@ def _ensure_plottable(*args) -> None: np.bool_, np.str_, ) - other_types = (datetime,) + other_types: tuple[type[object], ...] = (datetime,) if cftime is not None: cftime_datetime_types = (cftime.datetime,) other_types += cftime_datetime_types From b52333cf8c2e1b5c765c827690335dcd17f22bb0 Mon Sep 17 00:00:00 2001 From: Mick Date: Wed, 9 Nov 2022 23:46:37 +0100 Subject: [PATCH 5/8] Update xarray/plot/utils.py Co-authored-by: Illviljan <14371165+Illviljan@users.noreply.github.com> --- xarray/plot/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/xarray/plot/utils.py b/xarray/plot/utils.py index 7943452e89f..2c4ef803ee1 100644 --- a/xarray/plot/utils.py +++ b/xarray/plot/utils.py @@ -709,7 +709,8 @@ def _ensure_plottable(*args) -> None: ) if _valid_other_type(np.asarray(x), cftime_datetime_types): if nc_time_axis_available: - # needs to be imported or it will raise an error + # Register cftime datetypes to matplotlib.units.registry, + # otherwise matplotlib will raise an error: import nc_time_axis # noqa: F401 else: raise ImportError( From 35527c365dfee7d6ab531c2bdced6975f3b86d35 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 9 Nov 2022 22:48:23 +0000 Subject: [PATCH 6/8] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- xarray/plot/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xarray/plot/utils.py b/xarray/plot/utils.py index 2c4ef803ee1..e97243ab86c 100644 --- a/xarray/plot/utils.py +++ b/xarray/plot/utils.py @@ -709,7 +709,7 @@ def _ensure_plottable(*args) -> None: ) if _valid_other_type(np.asarray(x), cftime_datetime_types): if nc_time_axis_available: - # Register cftime datetypes to matplotlib.units.registry, + # Register cftime datetypes to matplotlib.units.registry, # otherwise matplotlib will raise an error: import nc_time_axis # noqa: F401 else: From 6f703f23f4fb67165f577eddcce972d019b19d04 Mon Sep 17 00:00:00 2001 From: Michael Niklas Date: Wed, 9 Nov 2022 23:59:14 +0100 Subject: [PATCH 7/8] fix mypy for real --- xarray/plot/utils.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/xarray/plot/utils.py b/xarray/plot/utils.py index e97243ab86c..68287185d68 100644 --- a/xarray/plot/utils.py +++ b/xarray/plot/utils.py @@ -691,11 +691,11 @@ def _ensure_plottable(*args) -> None: np.str_, ) other_types: tuple[type[object], ...] = (datetime,) - if cftime is not None: - cftime_datetime_types = (cftime.datetime,) - other_types += cftime_datetime_types - else: - cftime_datetime_types = () + cftime_datetime_types: tuple[type[object], ...] = ( + () if cftime is None else (cftime.datetime,) + ) + other_types += cftime_datetime_types + for x in args: if not ( _valid_numpy_subdtype(np.asarray(x), numpy_types) From b82337b103b50c4a3457dbaab0468ac81228b74f Mon Sep 17 00:00:00 2001 From: Michael Niklas Date: Thu, 10 Nov 2022 21:28:00 +0100 Subject: [PATCH 8/8] remove importskip of nc_time_axis --- xarray/tests/__init__.py | 1 - xarray/tests/test_plot.py | 24 +++++------------------- 2 files changed, 5 insertions(+), 20 deletions(-) diff --git a/xarray/tests/__init__.py b/xarray/tests/__init__.py index 584053d0213..5b2359a2d05 100644 --- a/xarray/tests/__init__.py +++ b/xarray/tests/__init__.py @@ -68,7 +68,6 @@ def _importorskip( has_cftime, requires_cftime = _importorskip("cftime") has_dask, requires_dask = _importorskip("dask") has_bottleneck, requires_bottleneck = _importorskip("bottleneck") -has_nc_time_axis, requires_nc_time_axis = _importorskip("nc_time_axis") has_rasterio, requires_rasterio = _importorskip("rasterio") has_zarr, requires_zarr = _importorskip("zarr") has_fsspec, requires_fsspec = _importorskip("fsspec") diff --git a/xarray/tests/test_plot.py b/xarray/tests/test_plot.py index cb0746179a0..88f03c03b40 100644 --- a/xarray/tests/test_plot.py +++ b/xarray/tests/test_plot.py @@ -14,6 +14,7 @@ import xarray as xr import xarray.plot as xplt from xarray import DataArray, Dataset +from xarray.core.utils import module_available from xarray.plot.dataarray_plot import _infer_interval_breaks from xarray.plot.dataset_plot import _infer_meta_data from xarray.plot.utils import ( @@ -29,14 +30,15 @@ from . import ( assert_array_equal, assert_equal, - has_nc_time_axis, requires_cartopy, requires_cftime, requires_matplotlib, - requires_nc_time_axis, requires_seaborn, ) +# this should not be imported to test if the automatic lazy import works +has_nc_time_axis = module_available("nc_time_axis") + # import mpl and change the backend before other mpl imports try: import matplotlib as mpl @@ -2823,8 +2825,8 @@ def test_datetime_plot2d(self) -> None: @pytest.mark.filterwarnings("ignore:setting an array element with a sequence") -@requires_nc_time_axis @requires_cftime +@pytest.mark.skipif(not has_nc_time_axis, reason="nc_time_axis is not installed") class TestCFDatetimePlot(PlotTestCase): @pytest.fixture(autouse=True) def setUp(self) -> None: @@ -3218,19 +3220,3 @@ def test_facetgrid_axes_raises_deprecation_warning() -> None: ds = xr.tutorial.scatter_example_dataset() g = ds.plot.scatter(x="A", y="B", col="x") g.axes - - -@requires_matplotlib -@requires_nc_time_axis -def test_plot_nc_time() -> None: - da = xr.DataArray( - range(10), - dims="time", - coords={ - "time": xr.cftime_range( - "1900-01-01", periods=10, calendar="noleap", freq="D" - ) - }, - ) - with figure_context(): - da.plot()