Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Make Axes.stem take at least one argument. #1139

Merged
merged 5 commits into from

5 participants

Damon McDougall Michael Droettboom Eric Firing Phil Elson Benjamin Root
Damon McDougall
Collaborator

When it takes only one, the abscissae default to np.arange(len(y))

This is my attempt at addressing #331.

lib/matplotlib/axes.py
((7 lines not shown))
"""
Create a stem plot.
- Call signature::
+ Valid call signatures::
Phil Elson Collaborator
pelson added a note

This change makes this docstring inconsistent with almost all others I am aware of. My feeling is that "Valid call signatures" should be reverted to "Call signature".

Benjamin Root Collaborator
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Damon McDougall
Collaborator

The method pcolor has two call signatures, so I just copied that. Thanks for pointing out the inconsistency.

Michael Droettboom
Owner

This is a nice fix.

Eric Firing
Owner

@mdboom, is this waiting for master to switch to 1.3?

Phil Elson
Collaborator

Does it need a changelog entry?

Eric Firing
Owner

Or better, api_changes.rst, if it is targeted for 1.2. If it missed the freeze and is therefore delayed until 1.3, then it is just as well to wait on the documentation changes, since they will likely need to be rebased anyway if done now. I'm just not clear on what can be merged now, and what should wait. I don't like having this PR backlog.

Damon McDougall
Collaborator

Ok, no problem. I will wait for further instruction :) As always, thanks for the feedback, you guys are really helpful.

Damon McDougall
Collaborator

What needs to be done here?

Damon McDougall
Collaborator

I updated the CHANGELOG. Let me know if there's anything else I can do.

Benjamin Root
Collaborator

This change can break code. Consider the following:

plt.stem(x, y, 'r--')

This was a perfectly valid call before that will no longer work.

Damon McDougall
Collaborator

@WeatherGod Thank you for trying this. I believe I have fixed this now. I also added a small test to check the kwargs as well, nothing huge. Just sanity checks.

lib/matplotlib/axes.py
@@ -5104,6 +5106,25 @@ def stem(self, x, y, linefmt='b-', markerfmt='bo', basefmt='r-',
if not self._hold: self.cla()
self.hold(True)
+ # Assume there's at least one data array
+ y = np.asarray(args[0], dtype=np.float)
+
+ # Try a second one
+ try:
+ second = np.asarray(args[1], dtype=np.float)
+ x, y = y, second
+ except:
Benjamin Root Collaborator

At the very least, catch Exception, but I would rather catch IndexError and whatever else might come from numpy (ValueError, maybe?)

Damon McDougall Collaborator

Yes, I wanted to catch both so didn't specify -- perhaps catching Exception also catches some magical python ponies that I am unaware of?

Damon McDougall Collaborator

The IndexError comes from args[1] being out of bounds. The ValueError comes from the casting to np.float being invalid. This is the case in your example of stem(x, 'r--').

Benjamin Root Collaborator

If you want to catch both, then do except (IndexError, ValueError):. There is a difference between except: and except Exception:. See here: http://docs.python.org/2/howto/doanddont.html#except

Damon McDougall Collaborator

That's helpful -- thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Benjamin Root WeatherGod commented on the diff
lib/matplotlib/tests/test_axes.py
@@ -901,6 +901,20 @@ def test_hist_stacked_weighted():
ax = fig.add_subplot(111)
ax.hist( (d1, d2), weights=(w1,w2), histtype="stepfilled", stacked=True)
+@cleanup
+def test_stem_args():
+ fig = plt.figure()
+ ax = fig.add_subplot(1, 1, 1)
+
+ x = range(10)
+ y = range(10)
+
+ # Test the call signatures
+ ax.stem(y)
+ ax.stem(x, y)
+ ax.stem(x, y, 'r--')
Benjamin Root Collaborator

This call (and the next) "works", but never actually gets the linefmt. It is left behind in *args. plot() and scatter() has to do this sort of crap too. Essentially, you go through all of the remaining positional args, supplying them for the defaults to the pops of the kwargs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Damon McDougall dmcdougall commented on the diff
lib/matplotlib/axes.py
((15 lines not shown))
+ second = np.arange(len(y))
+ x = second
+
+ # Popping some defaults
+ try:
+ linefmt = kwargs.pop('linefmt', args[0])
+ except IndexError:
+ linefmt = kwargs.pop('linefmt', 'b-')
+ try:
+ markerfmt = kwargs.pop('markerfmt', args[1])
+ except IndexError:
+ markerfmt = kwargs.pop('markerfmt', 'bo')
+ try:
+ basefmt = kwargs.pop('basefmt', args[2])
+ except IndexError:
+ basefmt = kwargs.pop('basefmt', 'r-')
Damon McDougall Collaborator

I don't like this big try/except block. Is there a better way of doing this?

Benjamin Root Collaborator
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Damon McDougall
Collaborator

Finally got some time to look at this. I did some method chasing and made the following three observations:

  1. Axes.plot(self, *args, **kwargs) calls self._get_lines(*args, **kwargs)
  2. self._get_lines = _process_plot_var_args(self)
  3. _process_plot_var_args is a class to do a bunch of heavy lifting for the args and kwargs passed to plot

There's quite a bit of heavy lifting done inside the _process_plot_var_args class. I guess I'm just not really sure how to use it effectively. I guess the end goal would be to have Axes.stem just make a call to self._get_lines and proceed with the returned line's properties.

I'll play and see what I can come up with over the weekend.

Damon McDougall
Collaborator

Ok, I've been playing a little bit with this. Basically, the way stem works at the moment is to parse a bunch of stuff and pass the baseline, markers, and stemlines all to plot. Then the three resulting line collections are returned as part of a StemContainer for easy post-processing.

Now, I think what @WeatherGod is suggesting is to basically pass everything to plot in a single call. While that is totally possible due to plot's ninja argument handling skills, I'd still have to parse the list of lines returned from plot and put them into a StemContainer to preserve the return type of stem (presumably, preserving the return type of stem is desirable since it doesn't break backwards compatibility). That is fine and dandy and is probably a good way to refactor the stem function to make only one call to plot. However, I see no other benefit to doing that over what is currently implemented in this PR. I still have to pop the kwargs to stem and get the defaults set correctly like I have done here, the only difference is, by refactoring to a single call to plot, extra foo needs to be done to process the stuff returned from plot and massage it into StemContainer.

I guess the question is: is it worth it? The way it is currently, with three separate calls to plot, it's very easy to keep the marker, base, and stems separate and lump them together into a StemContainer. I think I prefer this approach. Others may disagree.

@WeatherGod I'd especially like your opinion on this summary since you were the one to suggest this originally.

Damon McDougall
Collaborator

Actually, I do have an idea of how to improve this. I'll report back shortly.

Damon McDougall
Collaborator

Scratch that; my idea didn't work.

Benjamin Root
Collaborator

yeah, looking again, it is probably not worth it to go that unified plot route.

Damon McDougall
Collaborator

@WeatherGod Depends what you want out of the code. I can see benefits to both approaches, however I think this one is probably the most appropriate.

This is fine to merge, in my opinion.

Damon McDougall
Collaborator

Can someone push the green button, please? :)

Michael Droettboom mdboom merged commit 3b649cb into from
Damon McDougall dmcdougall deleted the branch
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Nov 13, 2012
  1. Damon McDougall

    Make Axes.stem take at least one argument.

    dmcdougall authored
    When it takes only one, the abscissae default to np.arange(len(y))
  2. Damon McDougall
  3. Damon McDougall
Commits on Nov 17, 2012
  1. Damon McDougall
  2. Damon McDougall
This page is out of date. Refresh to see the latest.
4 CHANGELOG
View
@@ -2,6 +2,10 @@
Also added some tests for the normalization class.
Till Stensitzki
+2012-11-12 Make axes.stem take at least one argument.
+ Uses a default range(n) when the first arg not provided.
+ Damon McDougall
+
2012-11-09 Make plt.subplot() without arguments act as subplot(111) - PI
2012-10-05 Add support for saving animations as animated GIFs. - JVDP
39 lib/matplotlib/axes.py
View
@@ -5074,13 +5074,13 @@ def broken_barh(self, xranges, yrange, **kwargs):
return col
- def stem(self, x, y, linefmt='b-', markerfmt='bo', basefmt='r-',
- bottom=None, label=None):
+ def stem(self, *args, **kwargs):
"""
Create a stem plot.
- Call signature::
+ Call signatures::
+ stem(y, linefmt='b-', markerfmt='bo', basefmt='r-')
stem(x, y, linefmt='b-', markerfmt='bo', basefmt='r-')
A stem plot plots vertical lines (using *linefmt*) at each *x*
@@ -5088,6 +5088,8 @@ def stem(self, x, y, linefmt='b-', markerfmt='bo', basefmt='r-',
using *markerfmt*. A horizontal line at 0 is is plotted using
*basefmt*.
+ If no *x* values are provided, the default is (0, 1, ..., len(y) - 1)
+
Return value is a tuple (*markerline*, *stemlines*,
*baseline*).
@@ -5104,6 +5106,37 @@ def stem(self, x, y, linefmt='b-', markerfmt='bo', basefmt='r-',
if not self._hold: self.cla()
self.hold(True)
+ # Assume there's at least one data array
+ y = np.asarray(args[0], dtype=np.float)
+ args = args[1:]
+
+ # Try a second one
+ try:
+ second = np.asarray(args[0], dtype=np.float)
+ x, y = y, second
+ args = args[1:]
+ except (IndexError, ValueError):
+ # The second array doesn't make sense, or it doesn't exist
+ second = np.arange(len(y))
+ x = second
+
+ # Popping some defaults
+ try:
+ linefmt = kwargs.pop('linefmt', args[0])
+ except IndexError:
+ linefmt = kwargs.pop('linefmt', 'b-')
+ try:
+ markerfmt = kwargs.pop('markerfmt', args[1])
+ except IndexError:
+ markerfmt = kwargs.pop('markerfmt', 'bo')
+ try:
+ basefmt = kwargs.pop('basefmt', args[2])
+ except IndexError:
+ basefmt = kwargs.pop('basefmt', 'r-')
Damon McDougall Collaborator

I don't like this big try/except block. Is there a better way of doing this?

Benjamin Root Collaborator
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+
+ bottom = kwargs.pop('bottom', None)
+ label = kwargs.pop('label', None)
+
markerline, = self.plot(x, y, markerfmt, label="_nolegend_")
if bottom is None:
10 lib/matplotlib/pyplot.py
View
@@ -3004,23 +3004,21 @@ def stackplot(x, *args, **kwargs):
draw_if_interactive()
finally:
ax.hold(washold)
-
+
return ret
# This function was autogenerated by boilerplate.py. Do not edit as
# changes will be lost
@_autogen_docstring(Axes.stem)
-def stem(x, y, linefmt='b-', markerfmt='bo', basefmt='r-', bottom=None,
- label=None, hold=None):
+def stem(*args, **kwargs):
ax = gca()
# allow callers to override the hold state by passing hold=True|False
washold = ax.ishold()
-
+ hold = kwargs.pop('hold', None)
if hold is not None:
ax.hold(hold)
try:
- ret = ax.stem(x, y, linefmt=linefmt, markerfmt=markerfmt,
- basefmt=basefmt, bottom=bottom, label=label)
+ ret = ax.stem(*args, **kwargs)
draw_if_interactive()
finally:
ax.hold(washold)
14 lib/matplotlib/tests/test_axes.py
View
@@ -901,6 +901,20 @@ def test_hist_stacked_weighted():
ax = fig.add_subplot(111)
ax.hist( (d1, d2), weights=(w1,w2), histtype="stepfilled", stacked=True)
+@cleanup
+def test_stem_args():
+ fig = plt.figure()
+ ax = fig.add_subplot(1, 1, 1)
+
+ x = range(10)
+ y = range(10)
+
+ # Test the call signatures
+ ax.stem(y)
+ ax.stem(x, y)
+ ax.stem(x, y, 'r--')
Benjamin Root Collaborator

This call (and the next) "works", but never actually gets the linefmt. It is left behind in *args. plot() and scatter() has to do this sort of crap too. Essentially, you go through all of the remaining positional args, supplying them for the defaults to the pops of the kwargs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ ax.stem(x, y, 'r--', basefmt='b--')
+
@image_comparison(baseline_images=['transparent_markers'], remove_text=True)
def test_transparent_markers():
np.random.seed(0)
Something went wrong with that request. Please try again.