Skip to content

Add x,y kwargs for plot.line(). #1926

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Mar 5, 2018
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions doc/plotting.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
--------------

Expand Down
2 changes: 2 additions & 0 deletions doc/whats-new.rst
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ Enhancements
rolling, windowed rolling, ND-rolling, short-time FFT and convolution.
(:issue:`1831`, :issue:`1142`, :issue:`819`)
By `Keisuke Fujii <https://github.com/fujiisoup>`_.
- :py:func:`~plot.line()` learned to make plots with data on x-axis if so specified. (:issue:`575`)
By `Deepak Cherian <https://github.com/dcherian>`_.

Bug fixes
~~~~~~~~~
Expand Down
70 changes: 51 additions & 19 deletions xarray/plot/plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,9 +176,10 @@ 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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably say something like "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
Expand All @@ -199,43 +200,74 @@ 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,
labels=list(darray.coords[huelabel].values),
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
Expand Down
41 changes: 40 additions & 1 deletion xarray/tests/test_plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,14 +91,44 @@ 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(2, 4)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You only have two cases now, so no need for 2x4 subplots.


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))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any point to this line?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No. I copied the test over from my debugging plots. I'll remove it (I assume you mean the set_title bit).


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)
Expand Down Expand Up @@ -279,6 +309,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()
Expand All @@ -288,6 +322,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')

Expand Down