A matplotlib.pyplot story: Mysterious plot() arguments order acceptance

I’ve noticed a change in the order of arguments when using the ax.plot() function in Matplotlib.

In a previous example, the output values (let’s call them squares) were positioned before any other argument, and I thought it was being called positionally, like this:

squares = [1, 4, 9, 16, 25] 
ax.plot(squares, linewidth=3)

However, as I was learning how to specify the input values for the squares, my book showed the argument for input values positioned before the output values, like this:

input_values = [1, 2, 3, 4, 5] 
squares = [1, 4, 9, 16, 25] 
ax.plot(input_values, squares, linewidth=3)

I was expecting to see this instead:

ax.plot(squares, input_values, linewidth=3) # Not a keyword argument, so why is Matplotlib expecting the input values argument first?

Or, because the input values start from 0 by default, I was expecting something like this:

ax.plot(squares, some_parameter_names=some_default_value, linewidth=3)

Maybe there is some parameter syntax that I am not aware of?

If the input values parameter has positional priority to the output values, how does Matplotlib know that my argument is intended to specify the output values when I only specify the output values and don’t specify the input values?"

Is there a lesson I missed?

Sure, I read that plot() is defaulting 0 as the first tick mark label for the x axis if only a series of values is fed into the function and that if the input values need be specified, the plot() function expects the first argument to be the input values when two series are specified. But how can it work like that?

plt.plot argument signature is quite complex and doesn’t easily fit into normal understandings. The docs are decent.

The basic idea is that the signature is plt.plot([x], y, [fmt], **kwargs), where [arg] means that arg is optional. This doesn’t mean you can pass those in as keyword arguments. Instead, if only 1 positional argument is given, it’s y. If two are given, it’s x and y [1]. If three are given, it’s x, y and fmt. This is similar to the builtin range([start], stop, [step]), where range(a) is the same as range(0, a, 1). For .plot x defaults to basically range(len(y))


  1. or y and fmt if the second argument is a string. ↩︎

3 Likes

Just to clarify one point, there’s no special parameter syntax that Axes.plot is using. It just uses runtime logic to given special interpretation to its arguments.

Very roughly, Axes.plot works something like this:

def plot(*args, **kwargs):
    if len(args) == 1:
        y = args[0]
        x = list(range(len(y)))
    elif len(args) == 2:
        x = args[0]
        y = args[1]

The actual code that matplotlib uses to interpret variable-length positional arguments is here:

2 Likes

Okay, so my main takeaway is that the parameters mentioned in the plot() function are positional, and those wrapped in square brackets are rendered optional by the brackets, right?
Past that, would it be wrong to infer that this is a useful way of making a parameter optional when working with positional parameters, as opposed to the keyword type?

Yes

I would generally not suggest designing an interface like this unless you are mirroring an interface from somewhere else (e.g. plot mirroring gnuplot and/or matlab, random.randrange mirroring range) or if the short form that is allowed by these weird optional positional arguments is very convenient (e.g. range(n)). Having optional arguments at the start of an argument list is going to be confusing most of the time.

1 Like

Thanks again. That was clear. I am going to have a look at the docs tomorrow and see if I can read them.

Got it. Thanks for the help today.

Just to be completely clear, the [...] is not python syntax, just @MegaIng’s description of the way that the function works.

Perhaps the best example (mentioned elsewhere) is the python builtin range which accepts range(n) meaning the same as range(0,n).

The brackets only appear in documentation, not in any actual code. They inform you that the corresponding arguments are optional when you call the function. You cannot write something like this to make your own functions work this way - instead you would need to use other logic like shown in @bschubert 's post.

“type” has a special meaning for us and isn’t applicable here. There isn’t really a specific jargon word here, but I’ll say “kind” (since that’s what the inspect standard library module uses).

There are a lot of ways to make parameters optional. The right way for your own function designs will depend on your own taste and discretion, and what you want the calling code to be able to write. It’s something that depends on experience and can’t be taught directly.

Sometimes, the approach that Matplotlib uses here will be quite pleasant for the user. Matplotlib uses it because the natural interpretation for plotting a single list of numbers is that they should represent the y-values, but if you have two separate lists for x-values and y-values, it makes sense to specify the x-values first.

But most of the time, if you have two parameters and the user specifies one argument, it will make much more sense to interpret that as a value for the first parameter and to use a default for the second.

Thanks, I was left a bit confused by the two different answers given by Cornelius and Schubert. It was not clear to me why I’d need runtime logic if I was already using the [arg] syntax to make a parameter optional. I understand now that [arg] was only descriptive of the fact that those parameters were made optional by some runtime logic, whose simplified version was mentioned by Schubert. Thanks Everyone.