diff --git a/doc/plotting.rst b/doc/plotting.rst index 2b816a24563..c85a54d783b 100644 --- a/doc/plotting.rst +++ b/doc/plotting.rst @@ -197,6 +197,16 @@ It is required to explicitly specify either Thus, we could have made the previous plot by specifying ``hue='lat'`` instead of ``x='time'``. If required, the automatic legend can be turned off using ``add_legend=False``. +Dimension along y-axis +~~~~~~~~~~~~~~~~~~~~~~ + +It is also possible to make line plots such that the data are on the x-axis and a dimension is on the y-axis. This can be done by specifying the appropriate ``y`` keyword argument. + +.. ipython:: python + + @savefig plotting_example_xy_kwarg.png + air.isel(time=10, lon=[10, 11]).plot.line(y='lat', hue='lon') + Two Dimensions -------------- diff --git a/doc/whats-new.rst b/doc/whats-new.rst index ab667ceba3f..6e819a5d34a 100644 --- a/doc/whats-new.rst +++ b/doc/whats-new.rst @@ -46,6 +46,8 @@ Enhancements rolling, windowed rolling, ND-rolling, short-time FFT and convolution. (:issue:`1831`, :issue:`1142`, :issue:`819`) By `Keisuke Fujii `_. +- :py:func:`~plot.line()` learned to make plots with data on x-axis if so specified. (:issue:`575`) + By `Deepak Cherian `_. Bug fixes ~~~~~~~~~ diff --git a/xarray/plot/plot.py b/xarray/plot/plot.py index b5e6d94a4d2..94ddc8c0535 100644 --- a/xarray/plot/plot.py +++ b/xarray/plot/plot.py @@ -176,9 +176,12 @@ def line(darray, *args, **kwargs): Axis on which to plot this figure. By default, use the current axis. Mutually exclusive with ``size`` and ``figsize``. hue : string, optional - Coordinate for which you want multiple lines plotted (2D inputs only). - x : string, optional - Coordinate for x axis. + Coordinate for which you want multiple lines plotted + (2D DataArrays only). + x, y : string, optional + Coordinates for x, y axis. Only one of these may be specified. + The other coordinate plots values from the DataArray on which this + plot method is called. add_legend : boolean, optional Add legend with y axis coordinates (2D inputs only). *args, **kwargs : optional @@ -199,35 +202,66 @@ def line(darray, *args, **kwargs): ax = kwargs.pop('ax', None) hue = kwargs.pop('hue', None) x = kwargs.pop('x', None) + y = kwargs.pop('y', None) add_legend = kwargs.pop('add_legend', True) ax = get_axis(figsize, size, aspect, ax) + error_msg = ('must be either None or one of ({0:s})' + .format(', '.join([repr(dd) for dd in darray.dims]))) + + if x is not None and x not in darray.dims: + raise ValueError('x ' + error_msg) + + if y is not None and y not in darray.dims: + raise ValueError('y ' + error_msg) + + if x is not None and y is not None: + raise ValueError('You cannot specify both x and y kwargs' + 'for line plots.') + if ndims == 1: - xlabel, = darray.dims - if x is not None and xlabel != x: - raise ValueError('Input does not have specified dimension' - ' {!r}'.format(x)) + dim, = darray.dims # get the only dimension name + + if (x is None and y is None) or x == dim: + xplt = darray.coords[dim] + yplt = darray + xlabel = dim + ylabel = darray.name - x = darray.coords[xlabel] + else: + yplt = darray.coords[dim] + xplt = darray + xlabel = darray.name + ylabel = dim else: - if x is None and hue is None: + if x is None and y is None and hue is None: raise ValueError('For 2D inputs, please specify either hue or x.') - xlabel, huelabel = _infer_xy_labels(darray=darray, x=x, y=hue) - x = darray.coords[xlabel] - darray = darray.transpose(xlabel, huelabel) + if y is None: + xlabel, huelabel = _infer_xy_labels(darray=darray, x=x, y=hue) + ylabel = darray.name + xplt = darray.coords[xlabel] + yplt = darray.transpose(xlabel, huelabel) - _ensure_plottable(x) + else: + ylabel, huelabel = _infer_xy_labels(darray=darray, x=y, y=hue) + xlabel = darray.name + xplt = darray.transpose(ylabel, huelabel) + yplt = darray.coords[ylabel] - primitive = ax.plot(x, darray, *args, **kwargs) + _ensure_plottable(xplt) - ax.set_xlabel(xlabel) - ax.set_title(darray._title_for_slice()) + primitive = ax.plot(xplt, yplt, *args, **kwargs) - if darray.name is not None: - ax.set_ylabel(darray.name) + if xlabel is not None: + ax.set_xlabel(xlabel) + + if ylabel is not None: + ax.set_ylabel(ylabel) + + ax.set_title(darray._title_for_slice()) if darray.ndim == 2 and add_legend: ax.legend(handles=primitive, @@ -235,7 +269,7 @@ def line(darray, *args, **kwargs): title=huelabel) # Rotate dates on xlabels - if np.issubdtype(x.dtype, np.datetime64): + if np.issubdtype(xplt.dtype, np.datetime64): ax.get_figure().autofmt_xdate() return primitive diff --git a/xarray/tests/test_plot.py b/xarray/tests/test_plot.py index 26ebcccc748..e509894f06e 100644 --- a/xarray/tests/test_plot.py +++ b/xarray/tests/test_plot.py @@ -91,14 +91,42 @@ def setUp(self): def test1d(self): self.darray[:, 0, 0].plot() - with raises_regex(ValueError, 'dimension'): + with raises_regex(ValueError, 'None'): self.darray[:, 0, 0].plot(x='dim_1') + def test_1d_x_y_kw(self): + z = np.arange(10) + da = DataArray(np.cos(z), dims=['z'], coords=[z], name='f') + + xy = [[None, None], + [None, 'z'], + ['z', None]] + + f, ax = plt.subplots(3, 1) + for aa, (x, y) in enumerate(xy): + da.plot(x=x, y=y, ax=ax.flat[aa]) + + with raises_regex(ValueError, 'cannot'): + da.plot(x='z', y='z') + + with raises_regex(ValueError, 'None'): + da.plot(x='f', y='z') + + with raises_regex(ValueError, 'None'): + da.plot(x='z', y='f') + def test_2d_line(self): with raises_regex(ValueError, 'hue'): self.darray[:, :, 0].plot.line() self.darray[:, :, 0].plot.line(hue='dim_1') + self.darray[:, :, 0].plot.line(x='dim_1') + self.darray[:, :, 0].plot.line(y='dim_1') + self.darray[:, :, 0].plot.line(x='dim_0', hue='dim_1') + self.darray[:, :, 0].plot.line(y='dim_0', hue='dim_1') + + with raises_regex(ValueError, 'cannot'): + self.darray[:, :, 0].plot.line(x='dim_1', y='dim_0', hue='dim_1') def test_2d_line_accepts_legend_kw(self): self.darray[:, :, 0].plot.line(x='dim_0', add_legend=False) @@ -279,6 +307,10 @@ def test_xlabel_is_index_name(self): self.darray.plot() assert 'period' == plt.gca().get_xlabel() + def test_no_label_name_on_x_axis(self): + self.darray.plot(y='period') + self.assertEqual('', plt.gca().get_xlabel()) + def test_no_label_name_on_y_axis(self): self.darray.plot() assert '' == plt.gca().get_ylabel() @@ -288,6 +320,11 @@ def test_ylabel_is_data_name(self): self.darray.plot() assert self.darray.name == plt.gca().get_ylabel() + def test_xlabel_is_data_name(self): + self.darray.name = 'temperature' + self.darray.plot(y='period') + self.assertEqual(self.darray.name, plt.gca().get_xlabel()) + def test_format_string(self): self.darray.plot.line('ro')