Skip to content

Commit 6d1434e

Browse files
jusegdcherian
authored andcommitted
Allow binned coordinates on 1D plots y-axis. (#3685)
* Allow binned coords on 1D plots y-axis. See #3571. * Add helper to convert intervals for 1d plots. * Add tests for line plots with x/y/x&y intervals. * Add tests for step plots with x/y intervals. * Simplify syntax in 1D intervals plot helper. * Document new handling of y-axis intervals.
1 parent 17b70ca commit 6d1434e

File tree

4 files changed

+74
-27
lines changed

4 files changed

+74
-27
lines changed

doc/whats-new.rst

+3
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,9 @@ Bug fixes
102102
By `Justus Magin <https://github.com/keewis>`_.
103103
- Fixed errors emitted by ``mypy --strict`` in modules that import xarray.
104104
(:issue:`3695`) by `Guido Imperiale <https://github.com/crusaderky>`_.
105+
- Fix plotting of binned coordinates on the y axis in :py:meth:`DataArray.plot`
106+
(line) and :py:meth:`DataArray.plot.step` plots (:issue:`#3571`,
107+
:pull:`3685`) by `Julien Seguinot <https://github.com/juseg>_`.
105108

106109
Documentation
107110
~~~~~~~~~~~~~

xarray/plot/plot.py

+6-27
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,11 @@
1717
_ensure_plottable,
1818
_infer_interval_breaks,
1919
_infer_xy_labels,
20-
_interval_to_double_bound_points,
21-
_interval_to_mid_points,
2220
_process_cmap_cbar_kwargs,
2321
_rescale_imshow_rgb,
22+
_resolve_intervals_1dplot,
2423
_resolve_intervals_2dplot,
2524
_update_axes,
26-
_valid_other_type,
2725
get_axis,
2826
import_matplotlib_pyplot,
2927
label_from_attrs,
@@ -296,29 +294,10 @@ def line(
296294
ax = get_axis(figsize, size, aspect, ax)
297295
xplt, yplt, hueplt, xlabel, ylabel, hue_label = _infer_line_data(darray, x, y, hue)
298296

299-
# Remove pd.Intervals if contained in xplt.values.
300-
if _valid_other_type(xplt.values, [pd.Interval]):
301-
# Is it a step plot? (see matplotlib.Axes.step)
302-
if kwargs.get("linestyle", "").startswith("steps-"):
303-
xplt_val, yplt_val = _interval_to_double_bound_points(
304-
xplt.values, yplt.values
305-
)
306-
# Remove steps-* to be sure that matplotlib is not confused
307-
kwargs["linestyle"] = (
308-
kwargs["linestyle"]
309-
.replace("steps-pre", "")
310-
.replace("steps-post", "")
311-
.replace("steps-mid", "")
312-
)
313-
if kwargs["linestyle"] == "":
314-
del kwargs["linestyle"]
315-
else:
316-
xplt_val = _interval_to_mid_points(xplt.values)
317-
yplt_val = yplt.values
318-
xlabel += "_center"
319-
else:
320-
xplt_val = xplt.values
321-
yplt_val = yplt.values
297+
# Remove pd.Intervals if contained in xplt.values and/or yplt.values.
298+
xplt_val, yplt_val, xlabel, ylabel, kwargs = _resolve_intervals_1dplot(
299+
xplt.values, yplt.values, xlabel, ylabel, kwargs
300+
)
322301

323302
_ensure_plottable(xplt_val, yplt_val)
324303

@@ -367,7 +346,7 @@ def step(darray, *args, where="pre", linestyle=None, ls=None, **kwargs):
367346
every *x* position, i.e. the interval ``[x[i], x[i+1])`` has the
368347
value ``y[i]``.
369348
- 'mid': Steps occur half-way between the *x* positions.
370-
Note that this parameter is ignored if the x coordinate consists of
349+
Note that this parameter is ignored if one coordinate consists of
371350
:py:func:`pandas.Interval` values, e.g. as a result of
372351
:py:func:`xarray.Dataset.groupby_bins`. In this case, the actual
373352
boundaries of the interval are used.

xarray/plot/utils.py

+36
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,42 @@ def _interval_to_double_bound_points(xarray, yarray):
453453
return xarray, yarray
454454

455455

456+
def _resolve_intervals_1dplot(xval, yval, xlabel, ylabel, kwargs):
457+
"""
458+
Helper function to replace the values of x and/or y coordinate arrays
459+
containing pd.Interval with their mid-points or - for step plots - double
460+
points which double the length.
461+
"""
462+
463+
# Is it a step plot? (see matplotlib.Axes.step)
464+
if kwargs.get("linestyle", "").startswith("steps-"):
465+
466+
# Convert intervals to double points
467+
if _valid_other_type(np.array([xval, yval]), [pd.Interval]):
468+
raise TypeError("Can't step plot intervals against intervals.")
469+
if _valid_other_type(xval, [pd.Interval]):
470+
xval, yval = _interval_to_double_bound_points(xval, yval)
471+
if _valid_other_type(yval, [pd.Interval]):
472+
yval, xval = _interval_to_double_bound_points(yval, xval)
473+
474+
# Remove steps-* to be sure that matplotlib is not confused
475+
del kwargs["linestyle"]
476+
477+
# Is it another kind of plot?
478+
else:
479+
480+
# Convert intervals to mid points and adjust labels
481+
if _valid_other_type(xval, [pd.Interval]):
482+
xval = _interval_to_mid_points(xval)
483+
xlabel += "_center"
484+
if _valid_other_type(yval, [pd.Interval]):
485+
yval = _interval_to_mid_points(yval)
486+
ylabel += "_center"
487+
488+
# return converted arguments
489+
return xval, yval, xlabel, ylabel, kwargs
490+
491+
456492
def _resolve_intervals_2dplot(val, func_name):
457493
"""
458494
Helper function to replace the values of a coordinate array containing

xarray/tests/test_plot.py

+29
Original file line numberDiff line numberDiff line change
@@ -426,9 +426,25 @@ def test_convenient_facetgrid_4d(self):
426426
d.plot(x="x", y="y", col="columns", ax=plt.gca())
427427

428428
def test_coord_with_interval(self):
429+
"""Test line plot with intervals."""
429430
bins = [-1, 0, 1, 2]
430431
self.darray.groupby_bins("dim_0", bins).mean(...).plot()
431432

433+
def test_coord_with_interval_x(self):
434+
"""Test line plot with intervals explicitly on x axis."""
435+
bins = [-1, 0, 1, 2]
436+
self.darray.groupby_bins("dim_0", bins).mean(...).plot(x="dim_0_bins")
437+
438+
def test_coord_with_interval_y(self):
439+
"""Test line plot with intervals explicitly on y axis."""
440+
bins = [-1, 0, 1, 2]
441+
self.darray.groupby_bins("dim_0", bins).mean(...).plot(y="dim_0_bins")
442+
443+
def test_coord_with_interval_xy(self):
444+
"""Test line plot with intervals on both x and y axes."""
445+
bins = [-1, 0, 1, 2]
446+
self.darray.groupby_bins("dim_0", bins).mean(...).dim_0_bins.plot()
447+
432448

433449
class TestPlot1D(PlotTestCase):
434450
@pytest.fixture(autouse=True)
@@ -511,10 +527,23 @@ def test_step(self):
511527
self.darray[0, 0].plot.step()
512528

513529
def test_coord_with_interval_step(self):
530+
"""Test step plot with intervals."""
514531
bins = [-1, 0, 1, 2]
515532
self.darray.groupby_bins("dim_0", bins).mean(...).plot.step()
516533
assert len(plt.gca().lines[0].get_xdata()) == ((len(bins) - 1) * 2)
517534

535+
def test_coord_with_interval_step_x(self):
536+
"""Test step plot with intervals explicitly on x axis."""
537+
bins = [-1, 0, 1, 2]
538+
self.darray.groupby_bins("dim_0", bins).mean(...).plot.step(x="dim_0_bins")
539+
assert len(plt.gca().lines[0].get_xdata()) == ((len(bins) - 1) * 2)
540+
541+
def test_coord_with_interval_step_y(self):
542+
"""Test step plot with intervals explicitly on y axis."""
543+
bins = [-1, 0, 1, 2]
544+
self.darray.groupby_bins("dim_0", bins).mean(...).plot.step(y="dim_0_bins")
545+
assert len(plt.gca().lines[0].get_xdata()) == ((len(bins) - 1) * 2)
546+
518547

519548
class TestPlotHistogram(PlotTestCase):
520549
@pytest.fixture(autouse=True)

0 commit comments

Comments
 (0)