-
-
Notifications
You must be signed in to change notification settings - Fork 8.2k
DOC: Tutorial on API shortcuts #30952
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
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,172 @@ | ||
| """ | ||
| ================ | ||
| Coding shortcuts | ||
| ================ | ||
|
|
||
| Matplotlib's primary and universal API is the :ref:`Axes interface <api_interfaces>`. | ||
| While it is clearly structured and powerful, it can sometimes feel overly verbose and | ||
| thus cumbersome to write. This page collects patterns for condensing the code | ||
| of the Axes-based API and achieving the same results with less typing for many simpler | ||
| plots. | ||
|
|
||
| .. note:: | ||
|
|
||
| The :ref:`pyplot interface <pyplot_interface>` is an alternative more compact | ||
| interface, and was historically modeled to be similar to MATLAB. It remains a | ||
| valid approach for those who want to use it. However, it has the disadvantage that | ||
| it achieves its brevity through implicit assumptions that you have to understand. | ||
|
|
||
| Since it follows a different paradigm, switching between the Axes interface and | ||
| the pyplot interface requires a shift of the mental model, and some code rewrite, | ||
| if the code develops to a point at which pyplot no longer provides enough | ||
| flexibility. | ||
|
|
||
| This tutorial goes the other way round, starting from the standard verbose Axes | ||
| interface and using its capabilities for shortcuts when you don't need all the | ||
| generality. | ||
|
|
||
| Let's assume we want to make a plot of the number of daylight hours per day over the | ||
| year in London. | ||
|
|
||
| The standard approach with the Axes interface looks like this. | ||
| """ | ||
|
|
||
| import matplotlib.pyplot as plt | ||
| import numpy as np | ||
|
|
||
| day = np.arange(365) | ||
| hours = 4.276 * np.sin(2 * np.pi * (day - 80)/365) + 12.203 | ||
|
|
||
| fig, ax = plt.subplots() | ||
| ax.plot(day, hours, color="orange") | ||
| ax.set_xlabel("day") | ||
| ax.set_ylabel("daylight hours") | ||
| ax.set_title("London") | ||
| plt.show() | ||
|
|
||
| # %% | ||
| # Note that we've included ``plt.show()`` here. This is needed to show the plot window | ||
| # when running from a command line or in a Python script. If you run a Jupyter notebook, | ||
| # this command is automatically executed at the end of each cell. | ||
| # | ||
| # For the rest of the tutorial, we'll assume that we are in a notebook and leave this | ||
| # out for brevity. Depending on your context you may still need it. | ||
| # | ||
| # If you instead want to save to a file, use ``fig.savefig("daylight.png")``. | ||
| # | ||
| # | ||
| # Collect Axes properties into a single ``set()`` call | ||
| # ==================================================== | ||
| # | ||
| # The properties of Matplotlib Artists can be modified through their respective | ||
| # ``set_*()`` methods. Artists additionally have a generic ``set()`` method, that takes | ||
| # keyword arguments and is equivalent to calling all the respective ``set_*()`` methods. | ||
| # :: | ||
| # | ||
| # ax.set_xlabel("day") | ||
| # ax.set_ylabel("daylight hours") | ||
| # | ||
| # can also be written as :: | ||
| # | ||
| # ax.set(xlabel="day", ylabel="daylight hours") | ||
| # | ||
| # This is the most simple and effective reduction you can do. With that we can shorten | ||
| # the above plot to | ||
|
|
||
| fig, ax = plt.subplots() | ||
| ax.plot(day, hours, color="orange") | ||
| ax.set(xlabel="day", ylabel="daylight hours", title="London") | ||
|
|
||
| # %% | ||
| # | ||
| # This works as long as you only need to pass one parameter to the ``set_*()`` function. | ||
| # The individual functions are still necessary if you want more control, e.g. | ||
| # ``set_title("London", fontsize=16)``. | ||
| # | ||
| # | ||
| # Not storing a reference to the figure | ||
| # ===================================== | ||
| # Another nuisance of ``fig, ax = plt.subplots()`` is that you always create a ``fig`` | ||
| # variable, even if you don't use it. A slightly shorter version would be using the | ||
| # standard variable for unused value in Python (``_``): ``_, ax = plt.subplots()``. | ||
| # However, that's only marginally better. | ||
| # | ||
| # You can work around this by separating figure and Axes creation and chaining them :: | ||
| # | ||
| # ax = plt.figure().add_subplot() | ||
| # | ||
| # This is a bit cleaner logically and has the slight advantage that you could set | ||
| # figure properties inline as well; e.g. ``plt.figure(facecolor="lightgoldenrod")``. | ||
| # But it has the disadvantage that it's longer than ``fig, ax = plt.subplots()``. | ||
| # | ||
| # You can still obtain the figure from the Axes if needed, e.g. :: | ||
| # | ||
| # ax.figure.savefig("daylight_hours.png") | ||
| # | ||
| # The example code now looks like this: | ||
|
|
||
| ax = plt.figure().add_subplot() | ||
| ax.plot(day, hours, color="orange") | ||
| ax.set(xlabel="day", ylabel="daylight hours", title="London") | ||
|
|
||
| # %% | ||
| # Define Axes properties during axes creation | ||
| # =========================================== | ||
| # The ``set_*`` methods as well as ``set`` modify existing objects. You can | ||
| # alternatively define them right at creation. Since you typically don't instantiate | ||
| # classes yourself in Matplotlib, but rather call some factory function that creates | ||
| # the object and wires it up correctly with the plot, this may seem less obvious. But | ||
| # in fact you just pass the desired properties to the factory functions. You are likely | ||
| # doing this already in some places without realizing. Consider the function to create | ||
| # a line :: | ||
| # | ||
| # ax.plot(x, y, color="orange") | ||
| # | ||
| # This is equivalent to :: | ||
| # | ||
| # line, = ax.plot(x, y) | ||
| # line.set_color("orange") | ||
| # | ||
| # The same can be done with functions that create Axes. | ||
|
|
||
| ax = plt.figure().add_subplot(xlabel="day", ylabel="daylight hours", title="London") | ||
| ax.plot(day, hours, color="orange") | ||
|
|
||
| # %% | ||
| # .. important:: | ||
| # The Axes properties are only accepted as keyword arguments by | ||
| # `.Figure.add_subplot`, which creates a single Axes. | ||
| # | ||
| # For `.Figure.subplots` and `.pyplot.subplots`, you'd have to pass the properties | ||
| # as a dict via the keyword argument ``subplot_kw``. The limitation here is that | ||
| # such parameters are given to all Axes. For example, if you need two subplots | ||
| # (``fig, (ax1, ax2) = plt.subplots(1, 2)``) with different labels, you have to | ||
| # set them individually. | ||
| # | ||
| # Defining Axes properties during creation is best used for single subplots or when | ||
| # all subplots share the same properties. | ||
| # | ||
| # | ||
| # Using implicit figure creation | ||
| # ============================== | ||
| # You can go even further by tapping into the pyplot logic and use `.pyplot.axes` to | ||
| # create the axes: | ||
|
|
||
| ax = plt.axes(xlabel="day", ylabel="daylight hours", title="London") | ||
| ax.plot(day, hours, color="orange") | ||
|
|
||
| # %% | ||
| # .. warning:: | ||
| # When using this, you have to be aware of the implicit figure semantics of pyplot. | ||
| # ``plt.axes()`` will only create a new figure if no figure exists. Otherwise, it | ||
| # will add the Axes to the current existing figure, which is likely not what you | ||
| # want. | ||
| # | ||
| # Not storing a reference to the Axes | ||
| # =================================== | ||
| # If you only need to visualize one dataset, you can append the plot command | ||
| # directly to the Axes creation. This may be useful e.g. in notebooks, | ||
| # where you want to create a plot with some configuration, but as little distracting | ||
| # code as possible: | ||
|
|
||
| plt.axes(xlabel="day", ylabel="daylight hours").plot(day, hours, color="orange") | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason to use
plt.axeshere rather thanplt.subplot?plt.subplotwould seem more consistent withsubplots/add_subplotin the rest of the tutorial.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is indeed a debatable aspect
plt.axes()andplt.subplot()are equivalent.I think the options are more or less:
flowchart LR A["A: fig, ax = plt.subplots()"] A --> B["B: ax = plt.figure().subplots()"] A --> C["C: ax = plt.figure().add_subplot()"] B --> D["D: ax = plt.axes()"] C --> D B --> E["E: ax = plt.subplot()"] C --> EI have chosen
A -> Cbecause that's what @anntzer uses andadd_subplot()accept Axes properties as kwags, whichsubplots()does not. Ideally there would beplt.figure().add_axes()orplt.figure().axes()but we are not in that place right now and this PR is scoped to use existing API.Favoring D over E is based on my dislike for
subplot(). IMHO creating a single subplot at a time is awkward, because you have implicit state - a setsubplot()calls is only meaningful if they are aligned. That doesn't matter for creating a single 111 subplot, but I don't want to advertise that function in general. Also,plt.subplots()/fig.subplots(),plt.subplot()look very much alike but they have subtly different semantics. YMMV, but I find somewhat more distinct names better in this case. I also like the similarityax = plt.axes(). Eventually,plt.axes()is shorter thanplt.subplot(); and brevity is the point of this tutorial 😄.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We do have
https://matplotlib.org/stable/api/_as_gen/matplotlib.figure.Figure.add_axes.html#matplotlib.figure.Figure.add_axes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Plt.axes is pretty ambiguous as to whether it is treated as a subplot, and hence placed using subplot_params or not. I looked at the source and it is created with add_subplot (unless you use a four-tuple) so thats probably fine, but the ambiguity is a bit confusing.
On the other hand the number of times I've mistyped subplot when I meant subplots is an argument against subplot!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh I see.
Figure.add_axesrequires at least one parameter so is not equivalent toplt.axes👀😬There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Exactly, one could discuss whether supporting argument-less
fig.add_axes()is reasonable, but that'd be a much heavier lift than describing existing API.Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tl;dr: I consider this rather a feature because it creates less mental overhead for the average user
I consider this rather a feature. The distinction between Axes and Subplot is an unnecessary burden for the average Matplotlib user (#18222). To clarify for those who are not into the topic (not sure if this is 100% exact, but the definition is good enough for here): A Subplot is an Axes that is created based on a GridSpec, determing the rough part of the figure this is plotted into, and SubplotParams, determining borders and margins of the Axes grid.
In former times this difference was more visible through the
Subplotclass, which we luckily got rid of #23573. Unfortunately, the name subplot must be kept around becauseplt.subplots()is our most-prominent function. To alleviate this, I consider and teach subplot as in informal concept - i.e. multiple figures arranged in a grid - as in "the figure consists of two (sub)plots side-by-side". One subplot being the limiting case is a just plausible enough explanation forfig, ax = plt.subplots(). It's called "subplots" because it supports creating one or more Axes.I'd argue that for creating a single full-figure Axes, the distinction between a 111 subplot and a manually places Axes is irrelevant for most users. The only difference is whether you can control its position via subplot_params. But most users won't do manual adjustment, and the need is thankfully significantly reduced through constrained_layout.
In that spirit:
plt.subplots()as the general solution is here to stay. We cannot do anything about it and justify it's naming because it can create multiple (sub)plots.fig.add_subplot()is the mildly odd one for creating just one full-sized Axes.fig.subplots()works, but I don't want to recommend it here because it cannot directly accept Axes properties (which is justified in that it creates both figure and Axes). I would use and argument-lessfig.add_axes()if that was there, but is not right now.plt.axes()is better thanplt.subplot(), because the informal subplot notion is awkward for one full-size (sub)plot.Some of the arguments above are a bit of a stretch, but this is my best solution to tell a comprehensible story on a historically complex and at times suboptimal API.