Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

RFC: new function-based API#14058

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Draft
tacaswell wants to merge3 commits intomatplotlib:main
base:main
Choose a base branch
Loading
fromtacaswell:rfc_new_API

Conversation

tacaswell
Copy link
Member

def ploting_func(*data_args, ax, **style_kwargs):
...

The first case has the advantage that it works in both python2 and
Copy link
Contributor

Choose a reason for hiding this comment

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

hopefully py2 won't really be a thing anymore by the time this is implemented :p

Copy link
MemberAuthor

Choose a reason for hiding this comment

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

indeed! If you dig into the commits on this, I started writing this in 2016...

and allow libraries to internally organize them selves using either of
the above Axes-is-required API. This avoids bike-shedding over the
API and eliminates the first-party 'special' namespace, but is a bit
magical.
Copy link
Contributor

Choose a reason for hiding this comment

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

Personally I find passing the axes as first argument muuuuuuch, much nicer (well, that may be because I essentially never use the pyplot layer which I understand may not be representative of most users).
Either we could use the "magic" decorator, or alternatively we could just have parallel namespacesplt.somefunc(..., ax=None) (None=gca()) &someothernamespace.somefunc(ax, ...) which would at least have the advantage of keeping reasonable signatures for all functions (with the "magic" decorator, inspect.signature can't represent the signature; which is not nice). Note that one namespace could be autogenerated from the other, e.g. inmod.py

@gen_pyplotlike  # registers to module_level registrydef func(ax, ...): ...@gen_pyplotlikedef otherfunc(ax, ...): ...pyplotlike = collect_pyplotlike()

and then one can doimport mod; mod.func(ax, ...) orfrom mod import pyplotlike as mod; mod.func(..., ax=ax).

Copy link
MemberAuthor

Choose a reason for hiding this comment

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

#4488 I tried something similar and it got rejected (and I sadly never followed up on making it its own package).

Copy link
Member

@timhoffmtimhoffmMay 1, 2019
edited
Loading

Choose a reason for hiding this comment

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

I find all three calling patterns valid approaches:

plt.plot([1, 2, 3])plt.plot(ax, [1, 2, 3])plt.plot([1, 2, 3], ax=ax)

and I welcome supporting all of them. Each one has it's use:

  • simple interactive use
  • interactive use with multiple axes (less to type thanax=ax)
  • programmatic use, where the data should be the first arguments for better readability.

I'm against creating multiple namespaces just for the sake of different calling patterns. For one, it's conceptually more difficult to tell people: "Usepyplot.plot() if, or useposax.plot(ax, ...) or usekwargs.plot(..., ax=ax)". Also you would have to create multiple variants of the documentation. While that could be automated, you still have the problem which one to link. It's much easier to once state "axes can be automatically determined, or passed as the first positional arguement, or passed as kwarg."

As@tacaswell has demonstrated that can all be resolved with a decorator.

I'm not quite sure if the actual function definition should be

@ensure_axdef func(ax, *data_args, **style_kwargs)

or

def func(*data_args, ax=ax, **style_kwargs)

I tend towards the latter because it's the syntactically correct signature for two out of the three cases. And it puts more emphasis on the data_args rather than on the axes. Also it has the big advantage, that it could be build intopyplot in a backward-compatible way. That way, we wouldn't need any new namespace.

Note also, that an axes context could be a valuable addition:

with plt.sca(ax):    plt.plot([1, 2, 3])    plt.xlabel('The x')    plt.ylabel('The y')

(maybe using a more telling name thansca()).

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure if I understand the goal here.
Is the goal to have libraries addplt.my_new_plotting_thing(...) andax.my_new_plotting_thing(...)?
That's all about entry points, right? Also: what exactly is the benefit of doing that?

Right now, my pattern is having an axes kwarg and if it'sNone I doplt.gca().
That's basically a single line, which might be slightly longer than adding theensure_ax decorator in terms of characters but not by much, and seems much easier to understand.

Right now I'm reasonably happy to dosome_plotting(ax=ax). Doingax.some_plotting instead might be nice, but I'm not entirely sure if that is the main goal of this proposal? Doingplt.some_plotting(...) instead of justsome_plotting(...) is just more characters, right? I guess it tells you by convention that if it starts withplt it'll modify the current axes? Though that's not even really true: plt.matshow creates a new figure.

Generally I prefer thinking about what code looks like when I use it first instead of thinking about the implementation first. Usually implementing whatever API we settle on is possible so it's more a question of what we want user code to look like.

Copy link
Member

Choose a reason for hiding this comment

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

Ithink the main goal is formatplotlib have plotting libraries that do

import matplotlib.basic as mbasicimport matplotlib.2d as m2dfig, ax = plt.subplots()mbasic.scatter(x, y, ax=ax)m2d.pcolormesh(x, y, z, ax=ax)

so the matplotlib library looks more like what user and third party libraries look like.

I think the goal would then be forax.scatter to just be a wrapper aroundmbasic.scatter.

But maybe I've completely misunderstood.

Copy link
Contributor

Choose a reason for hiding this comment

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

@jklymak Xo you're saying you want to change the matplotlib api to no longer doax.scatter andplt.scatter but doscatter(ax=ax).
That is very different from what I understood, but I'm also very confused ;)

Copy link
Member

Choose a reason for hiding this comment

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

Ithink its meant to be a third way. Ahem, I don't particularly want this, but I think the point is to make third-party libraries more plug-and-play with the main library. It also would allow us to have more domain-specific sub libraries without polluting theax.do_something name space. But maybe it has some deeper advantages as well?

amueller reacted with thumbs up emoji
Copy link
Contributor

Choose a reason for hiding this comment

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

Ok yeah I follow your interpretation. Let's see if that's what the others meant ;)

Copy link
Member

@jklymakjklymak left a comment

Choose a reason for hiding this comment

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

This could be made significantly more clear by usingax.scatter (or your choice) as a concrete example.

@amueller
Copy link
Contributor

amueller commentedJun 19, 2019
edited
Loading

I'm not sure if this is discussed somewhere, but is there a suggestion on what to do if a function wants to own a figure?

I feel the convention is to callgcf() but it's a bit unclear to me in what situations you'd callgcf() vs create a new figure, and if it ever makes sense to get a figure as an argument (and then clear it?).

[edit: replaced gca by gcf which is what I meant]

@timhoffm
Copy link
Member

I don't think this is discussed somewhere.

Generally, I discourage usingpyplot in functions (except for figure creation viaplt.subplots() orplt.figure()). The problem with other functions is that they implicitly rely on a global state, so the result of your function would depend on what the user has done before.

Instead, either create the figure and axes you need inside the function, or pass them in as parameters (depends a bit what you your function should do).

@amueller
Copy link
Contributor

amueller commentedJul 1, 2019
edited
Loading

@timhoffm can you give an example where passing in makes sense? I'm thinking about a function that creates several axes and does other things to the figure.
If the user plotted anything into a figure beforehand that might interact. If you require the user to pass an empty figure, why not create it yourself?

But basically you're saying not to usegcf. Your argument would also apply togca, though, and axis-level functions, right?
For those I feel using gca is a reasonable thing to do and what the functions in matplotlib and pandas do. So if I want my axes-level function to feel like matplotlib I'd have to usegca.

@timhoffm
Copy link
Member

If you're fully controlling the figure, then you can create it within your function. A typical pattern would be:

def my_figure(data, fig_kw):     fig, axs = plt.subplots(1, 2, **fig_kw)     # plot data into axs    axs[0].plot(data)    axs[1].bar(data)    return fig, axs

Whether to passfig_kw as a single dict or as keyword arguments is up to you.

You are only using pyplot to create a new figure. You don't use it's notion of current figure or axes (gcf/gca).

Passing the figure in as an argument makes sense when you don't necessarily control the full layout of the figure. An example ishttps://matplotlib.org/api/_as_gen/mpl_toolkits.axes_grid1.axes_grid.ImageGrid.html.ImageGrid needs adds a specific layout ofAxes to the figure. But you might still want to be able to combine these with regularAxes. Of course, the caller is responsible to provide a reasonable figure andrect. Otherwise, he might get overlap of theImageGrid and other parts of the figure.

The other case for passing in a figure is when you cannot rely on pyplot for figure creation. For example if your function should be able to be used for drawing a figure in a GUI Application. Such figures need a different set up for binding to the canvas and backend (pylot figure creation hides these details from you to make simple popup or notebook figures simple).

amueller reacted with thumbs up emoji

@tacaswell
Copy link
MemberAuthor

You are only using pyplot to create a new figure. You don't use it's notion of current figure or axes (gcf/gca).

But you will tap into it as the newly created figure will be the 'current figure' .

timhoffm reacted with thumbs up emoji

@anntzer
Copy link
Contributor

But you will tap into it as the newly created figure will be the 'current figure'.

Actually I think it should not, there should be some way of saying "create a new pyplot-managed figure but don't touch the current figure/axes", otherwise, say your 3rd-party plotting function creates a multi-axes plot; now which axes is the current axes becomes deeply coupled with the internals of the function or that function needs to be careful to set the current axes at the end (or perhaps even place all its axes where it wants in the current-axes stack...)

amueller reacted with thumbs up emoji

@amueller
Copy link
Contributor

@timhoffm@tacaswell@anntzer thank you for your input.

In some sense I feel that it might be nice for a library to control the state of the axes stack. But I'm also not sure what the expected behavior of a function should be.
Maybe it's best to discourage users from usinggca in most cases, unless they really know what's going on. I see no way that a user could benefit from usinggca without knowing the internals of a third-party plotting function, even if the function author had full control.

@dstansbydstansby added the status: needs comment/discussionneeds consensus on next step labelDec 15, 2019
@anntzer
Copy link
Contributor

I briefly thought about this again (due to the mention of function-based APIs in#9629 (comment)) and I think I realized another reason why I'm uncomfortable with function-based APIs which "promote" the use of the current-axes state (even if one can explicitly passax whether as first or as last argument):

Fairly often, an issue is reported in the tracker of people mixing pyplot and embedding in a GUI, and nearly always, the resolution is "don't mix pyplot and embedding". Which, in fact, is very easy to check: one can just see whether pyplot is imported. On the contrary, with function-based APIs whereax is optional, this becomes much more difficult to verify: one needs to painstakingly check that each call to a plotting function correctly passesax in; in other words, it becomes much easier to accidentally rely on global state (which I think we agree is bad when embedding).

@amueller
Copy link
Contributor

@anntzer this seems like a good argument to me, but it addresses a use-case that's quite far removed from my usual use of matplotlib (which is interactive use in jupyter).
I wonder if there's a way to make it easy to write interfaces for both communities.

Is your preferred solution never to use the current-axes state, so basically makeax a required argument?
I think you'll have a hard time convincing the data science community of that because they do a lot of interactive work (and it's not how any of the APIs there work right now, afaik).

@anntzer
Copy link
Contributor

I agree the use cases are fairly orthogonal (but I basically end up never using pyplot even for interactive work;ax = plt.figure().subplots() is not that onerous to type anyways) and we're clearly never getting rid of pyplot (no matter how much I would like it).
My point is rather that I think any API should make it "easy" to check that a program does not accidentally rely on the global state, because that can be an annoying source of bugs, which does show up regularly on the issue tracker (so it's not purely theoretical or purely in my twisted mind). One proposal I put forward (not necessarily the best solution, of course) was to autogenerate separate namespaces, per#14058 (comment).

@amueller
Copy link
Contributor

ax = plt.figure().subplots() is not that onerous, but if you call many plotting functions in Jupyter, you'd have to copy and paste that in every cell, and then pass it to the plotting function.

Basically I think of a notebook in which I call a bunch of pandas plotting functions, likedf.hist(),pd.plot.pairplot(df) and so on. It's very common to have dozens of cells with single line plot commands for EDA.

The namespace doesn't really work for methods attached to objects, though, which is very common in pandas and now also present in sklearn.

And I have no doubt that this is a real issue and making it easy to check for the use of global state is a goal I'd be totally on-board with.

anntzer reacted with thumbs up emoji

@github-actions
Copy link

Since this Pull Request has not been updated in 60 days, it has been marked "inactive." This does not mean that it will be closed, though it may be moved to a "Draft" state. This helps maintainers prioritize their reviewing efforts. You can pick the PR back up anytime - please ping us if you need a review or guidance to move the PR forward! If you do not plan on continuing the work, please let us know so that we can either find someone to take the PR over, or close it.

@github-actionsgithub-actionsbot added the status: inactiveMarked by the “Stale” Github Action labelJun 14, 2023
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment
Reviewers

@amuelleramuelleramueller left review comments

@anntzeranntzeranntzer left review comments

@jklymakjklymakjklymak left review comments

@timhoffmtimhoffmtimhoffm left review comments

At least 1 approving review is required to merge this pull request.

Assignees
No one assigned
Labels
Documentationstatus: inactiveMarked by the “Stale” Github Actionstatus: needs comment/discussionneeds consensus on next step
Projects
None yet
Milestone
No milestone
Development

Successfully merging this pull request may close these issues.

6 participants
@tacaswell@amueller@timhoffm@anntzer@jklymak@dstansby

[8]ページ先頭

©2009-2025 Movatter.jp