Skip to content

Commit 4983f1f

Browse files
dcherianfmaussion
authored andcommitted
Add x,y kwargs for plot.line(). (#1926)
* 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 * Respond to comments. * Only allow one of x or y to be specified. * fix docs * Follow suggestions. * Follow suggestions.
1 parent a32475b commit 4983f1f

File tree

4 files changed

+103
-20
lines changed

4 files changed

+103
-20
lines changed

doc/plotting.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,16 @@ It is required to explicitly specify either
197197
Thus, we could have made the previous plot by specifying ``hue='lat'`` instead of ``x='time'``.
198198
If required, the automatic legend can be turned off using ``add_legend=False``.
199199

200+
Dimension along y-axis
201+
~~~~~~~~~~~~~~~~~~~~~~
202+
203+
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.
204+
205+
.. ipython:: python
206+
207+
@savefig plotting_example_xy_kwarg.png
208+
air.isel(time=10, lon=[10, 11]).plot.line(y='lat', hue='lon')
209+
200210
Two Dimensions
201211
--------------
202212

doc/whats-new.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ Enhancements
4646
rolling, windowed rolling, ND-rolling, short-time FFT and convolution.
4747
(:issue:`1831`, :issue:`1142`, :issue:`819`)
4848
By `Keisuke Fujii <https://github.com/fujiisoup>`_.
49+
- :py:func:`~plot.line()` learned to make plots with data on x-axis if so specified. (:issue:`575`)
50+
By `Deepak Cherian <https://github.com/dcherian>`_.
4951

5052
Bug fixes
5153
~~~~~~~~~

xarray/plot/plot.py

Lines changed: 53 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -176,9 +176,12 @@ def line(darray, *args, **kwargs):
176176
Axis on which to plot this figure. By default, use the current axis.
177177
Mutually exclusive with ``size`` and ``figsize``.
178178
hue : string, optional
179-
Coordinate for which you want multiple lines plotted (2D inputs only).
180-
x : string, optional
181-
Coordinate for x axis.
179+
Coordinate for which you want multiple lines plotted
180+
(2D DataArrays only).
181+
x, y : string, optional
182+
Coordinates for x, y axis. Only one of these may be specified.
183+
The other coordinate plots values from the DataArray on which this
184+
plot method is called.
182185
add_legend : boolean, optional
183186
Add legend with y axis coordinates (2D inputs only).
184187
*args, **kwargs : optional
@@ -199,43 +202,74 @@ def line(darray, *args, **kwargs):
199202
ax = kwargs.pop('ax', None)
200203
hue = kwargs.pop('hue', None)
201204
x = kwargs.pop('x', None)
205+
y = kwargs.pop('y', None)
202206
add_legend = kwargs.pop('add_legend', True)
203207

204208
ax = get_axis(figsize, size, aspect, ax)
205209

210+
error_msg = ('must be either None or one of ({0:s})'
211+
.format(', '.join([repr(dd) for dd in darray.dims])))
212+
213+
if x is not None and x not in darray.dims:
214+
raise ValueError('x ' + error_msg)
215+
216+
if y is not None and y not in darray.dims:
217+
raise ValueError('y ' + error_msg)
218+
219+
if x is not None and y is not None:
220+
raise ValueError('You cannot specify both x and y kwargs'
221+
'for line plots.')
222+
206223
if ndims == 1:
207-
xlabel, = darray.dims
208-
if x is not None and xlabel != x:
209-
raise ValueError('Input does not have specified dimension'
210-
' {!r}'.format(x))
224+
dim, = darray.dims # get the only dimension name
225+
226+
if (x is None and y is None) or x == dim:
227+
xplt = darray.coords[dim]
228+
yplt = darray
229+
xlabel = dim
230+
ylabel = darray.name
211231

212-
x = darray.coords[xlabel]
232+
else:
233+
yplt = darray.coords[dim]
234+
xplt = darray
235+
xlabel = darray.name
236+
ylabel = dim
213237

214238
else:
215-
if x is None and hue is None:
239+
if x is None and y is None and hue is None:
216240
raise ValueError('For 2D inputs, please specify either hue or x.')
217241

218-
xlabel, huelabel = _infer_xy_labels(darray=darray, x=x, y=hue)
219-
x = darray.coords[xlabel]
220-
darray = darray.transpose(xlabel, huelabel)
242+
if y is None:
243+
xlabel, huelabel = _infer_xy_labels(darray=darray, x=x, y=hue)
244+
ylabel = darray.name
245+
xplt = darray.coords[xlabel]
246+
yplt = darray.transpose(xlabel, huelabel)
221247

222-
_ensure_plottable(x)
248+
else:
249+
ylabel, huelabel = _infer_xy_labels(darray=darray, x=y, y=hue)
250+
xlabel = darray.name
251+
xplt = darray.transpose(ylabel, huelabel)
252+
yplt = darray.coords[ylabel]
223253

224-
primitive = ax.plot(x, darray, *args, **kwargs)
254+
_ensure_plottable(xplt)
225255

226-
ax.set_xlabel(xlabel)
227-
ax.set_title(darray._title_for_slice())
256+
primitive = ax.plot(xplt, yplt, *args, **kwargs)
228257

229-
if darray.name is not None:
230-
ax.set_ylabel(darray.name)
258+
if xlabel is not None:
259+
ax.set_xlabel(xlabel)
260+
261+
if ylabel is not None:
262+
ax.set_ylabel(ylabel)
263+
264+
ax.set_title(darray._title_for_slice())
231265

232266
if darray.ndim == 2 and add_legend:
233267
ax.legend(handles=primitive,
234268
labels=list(darray.coords[huelabel].values),
235269
title=huelabel)
236270

237271
# Rotate dates on xlabels
238-
if np.issubdtype(x.dtype, np.datetime64):
272+
if np.issubdtype(xplt.dtype, np.datetime64):
239273
ax.get_figure().autofmt_xdate()
240274

241275
return primitive

xarray/tests/test_plot.py

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,14 +91,42 @@ def setUp(self):
9191
def test1d(self):
9292
self.darray[:, 0, 0].plot()
9393

94-
with raises_regex(ValueError, 'dimension'):
94+
with raises_regex(ValueError, 'None'):
9595
self.darray[:, 0, 0].plot(x='dim_1')
9696

97+
def test_1d_x_y_kw(self):
98+
z = np.arange(10)
99+
da = DataArray(np.cos(z), dims=['z'], coords=[z], name='f')
100+
101+
xy = [[None, None],
102+
[None, 'z'],
103+
['z', None]]
104+
105+
f, ax = plt.subplots(3, 1)
106+
for aa, (x, y) in enumerate(xy):
107+
da.plot(x=x, y=y, ax=ax.flat[aa])
108+
109+
with raises_regex(ValueError, 'cannot'):
110+
da.plot(x='z', y='z')
111+
112+
with raises_regex(ValueError, 'None'):
113+
da.plot(x='f', y='z')
114+
115+
with raises_regex(ValueError, 'None'):
116+
da.plot(x='z', y='f')
117+
97118
def test_2d_line(self):
98119
with raises_regex(ValueError, 'hue'):
99120
self.darray[:, :, 0].plot.line()
100121

101122
self.darray[:, :, 0].plot.line(hue='dim_1')
123+
self.darray[:, :, 0].plot.line(x='dim_1')
124+
self.darray[:, :, 0].plot.line(y='dim_1')
125+
self.darray[:, :, 0].plot.line(x='dim_0', hue='dim_1')
126+
self.darray[:, :, 0].plot.line(y='dim_0', hue='dim_1')
127+
128+
with raises_regex(ValueError, 'cannot'):
129+
self.darray[:, :, 0].plot.line(x='dim_1', y='dim_0', hue='dim_1')
102130

103131
def test_2d_line_accepts_legend_kw(self):
104132
self.darray[:, :, 0].plot.line(x='dim_0', add_legend=False)
@@ -279,6 +307,10 @@ def test_xlabel_is_index_name(self):
279307
self.darray.plot()
280308
assert 'period' == plt.gca().get_xlabel()
281309

310+
def test_no_label_name_on_x_axis(self):
311+
self.darray.plot(y='period')
312+
self.assertEqual('', plt.gca().get_xlabel())
313+
282314
def test_no_label_name_on_y_axis(self):
283315
self.darray.plot()
284316
assert '' == plt.gca().get_ylabel()
@@ -288,6 +320,11 @@ def test_ylabel_is_data_name(self):
288320
self.darray.plot()
289321
assert self.darray.name == plt.gca().get_ylabel()
290322

323+
def test_xlabel_is_data_name(self):
324+
self.darray.name = 'temperature'
325+
self.darray.plot(y='period')
326+
self.assertEqual(self.darray.name, plt.gca().get_xlabel())
327+
291328
def test_format_string(self):
292329
self.darray.plot.line('ro')
293330

0 commit comments

Comments
 (0)