From d404cea8060ce3b14ecd8f45de9e0959346c9b88 Mon Sep 17 00:00:00 2001 From: dcherian Date: Sat, 16 Dec 2017 18:44:15 -0800 Subject: [PATCH 1/6] Add x,y kwargs for plot.line(). Supports both 1D and 2D DataArrays as input. Change variable names to make code clearer: 1. set xplt, yplt to be values that are passed to ax.plot() 2. xlabel, ylabel are axes labels 3. xdim, ydim are dimension names --- doc/plotting.rst | 10 ++++++ doc/whats-new.rst | 2 ++ xarray/plot/plot.py | 75 +++++++++++++++++++++++++++++---------- xarray/tests/test_plot.py | 39 +++++++++++++++++++- 4 files changed, 107 insertions(+), 19 deletions(-) diff --git a/doc/plotting.rst b/doc/plotting.rst index 2b816a24563..a098ff5cbca 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``. +Data along x-axis +~~~~~~~~~~~~~~~~~ + +It is also possible to make line plots such that the data are on the x-axis and a co-ordinate is on the y-axis. This can be done by specifying the ``x`` and ``y`` keyword arguments. + +.. 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..ad9155c440a 100644 --- a/doc/whats-new.rst +++ b/doc/whats-new.rst @@ -141,6 +141,8 @@ Enhancements encoding/decoding of datetimes with non-standard calendars without the netCDF4 dependency (:issue:`1084`). By `Joe Hamman `_. +- :py:func:`~plot.line()` learned to make plots with data on x-axis if so specified. + By `Deepak Cherian `_. .. _Zarr: http://zarr.readthedocs.io/ diff --git a/xarray/plot/plot.py b/xarray/plot/plot.py index b5e6d94a4d2..46e6ec4de0e 100644 --- a/xarray/plot/plot.py +++ b/xarray/plot/plot.py @@ -176,9 +176,13 @@ 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). + Coordinate for which you want multiple lines plotted + (2D DataArrays only). x : string, optional - Coordinate for x axis. + 1D and 2D DataArrays: Coordinate for x axis. + y : string, optional + 1D DataArray: Can be coordinate name or DataArray.name + 2D DataArray: Coordinate for y axis. add_legend : boolean, optional Add legend with y axis coordinates (2D inputs only). *args, **kwargs : optional @@ -199,35 +203,70 @@ 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) 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 + + error_msg = 'must be either None or %r' % dim + if darray.name: + error_msg += ' or %r.' % darray.name + + if x not in [None, dim, darray.name]: + raise ValueError('x ' + error_msg) + + if y not in [None, dim, darray.name]: + raise ValueError('y ' + error_msg) - x = darray.coords[xlabel] + if x is not None and y is not None and x == y: + raise ValueError('Cannot make a plot with x=%r and y=%r' % (x, y)) + + if (x is None and y is None) or x == dim or y is darray.name: + xplt = darray.coords[dim] + yplt = darray + xlabel = dim + ylabel = darray.name + + elif y == dim or x is darray.name: + 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: + if x is not None and x is not darray.name: + raise ValueError('Cannot make a plot with x=%r and y=%r ' + % (x, y)) - primitive = ax.plot(x, darray, *args, **kwargs) + ylabel, huelabel = _infer_xy_labels(darray=darray, x=y, y=hue) + xlabel = darray.name + xplt = darray.transpose(ylabel, huelabel) + yplt = darray.coords[ylabel] - ax.set_xlabel(xlabel) - ax.set_title(darray._title_for_slice()) + _ensure_plottable(xplt) - if darray.name is not None: - ax.set_ylabel(darray.name) + primitive = ax.plot(xplt, yplt, *args, **kwargs) + + 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 +274,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..874b72e15dc 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, 'f'], + [None, 'z'], + ['f', None], + ['z', None], + ['z', 'f'], + ['f', 'z']] + + f, ax = plt.subplots(2, 4) + + for aa, (x, y) in enumerate(xy): + da.plot(x=x, y=y, ax=ax.flat[aa]) + ax.flat[aa].set_title('x=' + str(x) + ' | '+'y='+str(y)) + + with raises_regex(ValueError, 'Cannot'): + da.plot(x='z', y='z') + 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(x=self.darray.name) + self.assertEqual(self.darray.name, plt.gca().get_xlabel()) + def test_format_string(self): self.darray.plot.line('ro') From c1d71351d0e97c0bf06f72d9d122f467148c7272 Mon Sep 17 00:00:00 2001 From: dcherian Date: Tue, 20 Feb 2018 15:08:33 -0800 Subject: [PATCH 2/6] Respond to comments. --- doc/plotting.rst | 6 +++--- doc/whats-new.rst | 2 +- xarray/plot/plot.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/plotting.rst b/doc/plotting.rst index a098ff5cbca..daa3dfa8c1c 100644 --- a/doc/plotting.rst +++ b/doc/plotting.rst @@ -197,10 +197,10 @@ 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``. -Data along x-axis -~~~~~~~~~~~~~~~~~ +Coordinate along y-axis +~~~~~~~~~~~~~~~~~~~~~~~ -It is also possible to make line plots such that the data are on the x-axis and a co-ordinate is on the y-axis. This can be done by specifying the ``x`` and ``y`` keyword arguments. +It is also possible to make line plots such that the data are on the x-axis and a coordinate is on the y-axis. This can be done by specifying the ``x`` and ``y`` keyword arguments. .. ipython:: python diff --git a/doc/whats-new.rst b/doc/whats-new.rst index ad9155c440a..f1046626213 100644 --- a/doc/whats-new.rst +++ b/doc/whats-new.rst @@ -141,7 +141,7 @@ Enhancements encoding/decoding of datetimes with non-standard calendars without the netCDF4 dependency (:issue:`1084`). By `Joe Hamman `_. -- :py:func:`~plot.line()` learned to make plots with data on x-axis if so specified. +- :py:func:`~plot.line()` learned to make plots with data on x-axis if so specified. (:issue:`575`) By `Deepak Cherian `_. .. _Zarr: http://zarr.readthedocs.io/ diff --git a/xarray/plot/plot.py b/xarray/plot/plot.py index 46e6ec4de0e..3ed9990645b 100644 --- a/xarray/plot/plot.py +++ b/xarray/plot/plot.py @@ -224,13 +224,13 @@ def line(darray, *args, **kwargs): if x is not None and y is not None and x == y: raise ValueError('Cannot make a plot with x=%r and y=%r' % (x, y)) - if (x is None and y is None) or x == dim or y is darray.name: + if (x is None and y is None) or x == dim or y == darray.name: xplt = darray.coords[dim] yplt = darray xlabel = dim ylabel = darray.name - elif y == dim or x is darray.name: + elif y == dim or x == darray.name: yplt = darray.coords[dim] xplt = darray xlabel = darray.name From 2bf23f5d13f1540eae534e45a818dde4315470b3 Mon Sep 17 00:00:00 2001 From: dcherian Date: Wed, 21 Feb 2018 09:38:07 -0800 Subject: [PATCH 3/6] Only allow one of x or y to be specified. --- xarray/plot/plot.py | 36 ++++++++++++++---------------------- xarray/tests/test_plot.py | 20 +++++++++++--------- 2 files changed, 25 insertions(+), 31 deletions(-) diff --git a/xarray/plot/plot.py b/xarray/plot/plot.py index 3ed9990645b..ca928c07865 100644 --- a/xarray/plot/plot.py +++ b/xarray/plot/plot.py @@ -178,11 +178,8 @@ def line(darray, *args, **kwargs): hue : string, optional Coordinate for which you want multiple lines plotted (2D DataArrays only). - x : string, optional - 1D and 2D DataArrays: Coordinate for x axis. - y : string, optional - 1D DataArray: Can be coordinate name or DataArray.name - 2D DataArray: Coordinate for y axis. + x, y : string, optional + Coordinates for x, y axis. Only one of these may be specified. add_legend : boolean, optional Add legend with y axis coordinates (2D inputs only). *args, **kwargs : optional @@ -208,29 +205,28 @@ def line(darray, *args, **kwargs): ax = get_axis(figsize, size, aspect, ax) - if ndims == 1: - dim, = darray.dims # get the only dimension name + error_msg = ('must be either None or one of ({0:s})' + .format(', '.join(['\'' + dd + '\'' for dd in darray.dims]))) - error_msg = 'must be either None or %r' % dim - if darray.name: - error_msg += ' or %r.' % darray.name + if x is not None and x not in darray.dims: + raise ValueError('x ' + error_msg) - if x not in [None, dim, darray.name]: - raise ValueError('x ' + error_msg) + if y is not None and y not in darray.dims: + raise ValueError('y ' + error_msg) - if y not in [None, dim, darray.name]: - 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.') - if x is not None and y is not None and x == y: - raise ValueError('Cannot make a plot with x=%r and y=%r' % (x, y)) + if ndims == 1: + dim, = darray.dims # get the only dimension name - if (x is None and y is None) or x == dim or y == darray.name: + if (x is None and y is None) or x == dim: xplt = darray.coords[dim] yplt = darray xlabel = dim ylabel = darray.name - elif y == dim or x == darray.name: + else: yplt = darray.coords[dim] xplt = darray xlabel = darray.name @@ -247,10 +243,6 @@ def line(darray, *args, **kwargs): yplt = darray.transpose(xlabel, huelabel) else: - if x is not None and x is not darray.name: - raise ValueError('Cannot make a plot with x=%r and y=%r ' - % (x, y)) - ylabel, huelabel = _infer_xy_labels(darray=darray, x=y, y=hue) xlabel = darray.name xplt = darray.transpose(ylabel, huelabel) diff --git a/xarray/tests/test_plot.py b/xarray/tests/test_plot.py index 874b72e15dc..fdedad9445d 100644 --- a/xarray/tests/test_plot.py +++ b/xarray/tests/test_plot.py @@ -99,22 +99,24 @@ def test_1d_x_y_kw(self): da = DataArray(np.cos(z), dims=['z'], coords=[z], name='f') xy = [[None, None], - [None, 'f'], [None, 'z'], - ['f', None], - ['z', None], - ['z', 'f'], - ['f', 'z']] + ['z', None]] f, ax = plt.subplots(2, 4) for aa, (x, y) in enumerate(xy): da.plot(x=x, y=y, ax=ax.flat[aa]) - ax.flat[aa].set_title('x=' + str(x) + ' | '+'y='+str(y)) + ax.flat[aa].set_title('x=' + str(x) + ' | ' + 'y=' + str(y)) - with raises_regex(ValueError, 'Cannot'): + 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() @@ -125,7 +127,7 @@ def test_2d_line(self): 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'): + 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): @@ -322,7 +324,7 @@ def test_ylabel_is_data_name(self): def test_xlabel_is_data_name(self): self.darray.name = 'temperature' - self.darray.plot(x=self.darray.name) + self.darray.plot(y='period') self.assertEqual(self.darray.name, plt.gca().get_xlabel()) def test_format_string(self): From 81f5a65fb226eaffab2170cb850630a5ba0bd725 Mon Sep 17 00:00:00 2001 From: dcherian Date: Mon, 26 Feb 2018 22:22:54 -0800 Subject: [PATCH 4/6] fix docs --- doc/plotting.rst | 6 +++--- doc/whats-new.rst | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/plotting.rst b/doc/plotting.rst index daa3dfa8c1c..c85a54d783b 100644 --- a/doc/plotting.rst +++ b/doc/plotting.rst @@ -197,10 +197,10 @@ 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``. -Coordinate along y-axis -~~~~~~~~~~~~~~~~~~~~~~~ +Dimension along y-axis +~~~~~~~~~~~~~~~~~~~~~~ -It is also possible to make line plots such that the data are on the x-axis and a coordinate is on the y-axis. This can be done by specifying the ``x`` and ``y`` keyword arguments. +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 diff --git a/doc/whats-new.rst b/doc/whats-new.rst index f1046626213..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 ~~~~~~~~~ @@ -141,8 +143,6 @@ Enhancements encoding/decoding of datetimes with non-standard calendars without the netCDF4 dependency (:issue:`1084`). By `Joe Hamman `_. -- :py:func:`~plot.line()` learned to make plots with data on x-axis if so specified. (:issue:`575`) - By `Deepak Cherian `_. .. _Zarr: http://zarr.readthedocs.io/ From 3cd49cbbf9d0346ff687c3acfc23a8013c143e2b Mon Sep 17 00:00:00 2001 From: dcherian Date: Wed, 28 Feb 2018 19:57:22 -0800 Subject: [PATCH 5/6] Follow suggestions. --- xarray/plot/plot.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/xarray/plot/plot.py b/xarray/plot/plot.py index ca928c07865..5d1ba5f2b13 100644 --- a/xarray/plot/plot.py +++ b/xarray/plot/plot.py @@ -206,7 +206,7 @@ def line(darray, *args, **kwargs): ax = get_axis(figsize, size, aspect, ax) error_msg = ('must be either None or one of ({0:s})' - .format(', '.join(['\'' + dd + '\'' for dd in darray.dims]))) + .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) @@ -215,7 +215,8 @@ def line(darray, *args, **kwargs): 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.') + raise ValueError('You cannot specify both x and y kwargs' + 'for line plots.') if ndims == 1: dim, = darray.dims # get the only dimension name From cf2526071863c1b001c7b1a333ead7d81dcf9f3c Mon Sep 17 00:00:00 2001 From: dcherian Date: Thu, 1 Mar 2018 09:06:09 -0800 Subject: [PATCH 6/6] Follow suggestions. --- xarray/plot/plot.py | 2 ++ xarray/tests/test_plot.py | 4 +--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/xarray/plot/plot.py b/xarray/plot/plot.py index 5d1ba5f2b13..94ddc8c0535 100644 --- a/xarray/plot/plot.py +++ b/xarray/plot/plot.py @@ -180,6 +180,8 @@ def line(darray, *args, **kwargs): (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 diff --git a/xarray/tests/test_plot.py b/xarray/tests/test_plot.py index fdedad9445d..e509894f06e 100644 --- a/xarray/tests/test_plot.py +++ b/xarray/tests/test_plot.py @@ -102,11 +102,9 @@ def test_1d_x_y_kw(self): [None, 'z'], ['z', None]] - f, ax = plt.subplots(2, 4) - + f, ax = plt.subplots(3, 1) for aa, (x, y) in enumerate(xy): da.plot(x=x, y=y, ax=ax.flat[aa]) - ax.flat[aa].set_title('x=' + str(x) + ' | ' + 'y=' + str(y)) with raises_regex(ValueError, 'cannot'): da.plot(x='z', y='z')