Skip to content
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

API Discussion for Figures and Axes #826

Closed
bbengfort opened this issue Apr 24, 2019 · 10 comments
Closed

API Discussion for Figures and Axes #826

bbengfort opened this issue Apr 24, 2019 · 10 comments
Labels
level: expert deep knowledge of packages required priority: high should be done before next release type: question more information is required
Milestone

Comments

@bbengfort
Copy link
Member

This issue is to discuss the open letter regarding Yellowbrick's API roadmap. To summarize, we currently attempt to only manage matplotlib Axes objects so that visualizers can be embedded into more complex plots and reports. However, many of our visualizers are getting increasingly complex, requiring subplots of their own. For a complete discussion of the API issue please see:

https://www.scikit-yb.org/en/develop/api/figures.html

The questions at hand are:

  1. Like Seaborn, should YB have two classes of visualizer, one that wraps an axes and one that wraps a figure?
  2. Should we go all in on the AxesGrid toolkit and continue to restrict our use of the figure, will this method be supported in the long run?
@bbengfort bbengfort added the type: question more information is required label Apr 24, 2019
@tacaswell
Copy link

This is going to be a bit of a rambling answer (sorry).

This is a sticky API problem (that gets worse if you want to think about how to return "artists" that span multiple Axes (I want to change the color of / data in the residuals plot, I need one to be able to grab all of the parts of it) ).

Generally, visualizers should behave as though they are a plot that as part of a larger figure.

💯 agree with this.

AxesGrid is not going anywhere and there is currently on going work to document and harden that part of the code base.

Riffing a bit, it sounds like the information that should go into the visualizers is "this area on this figure is for your use" which is currently being expressed as an Axes object that gets laundered through make_axes_locatable. Taking in a GridSpec or and Axes and internally shimming them to be the same is probably a reasonable API (and also allow Figure which lets it assume it owns the whole thing).

Another axis of composition that maybe of interest is to put multiple models on the same (set of) axes. In that case it may be worth having a compatibility layer with an api that is half dictionary / half GridSpec. Something like

def viz_method(data, fit, **style, axes_source):
    # make named Axes objects where we want them
    if not len(axes_source):
        # The position would be relative to the space given to the axes_source
        axes_source.add('data', (.1, .2, .8, .7))  # left, bottom, width, height
        # want to share the xaxis of data and residual to keep synched
        axes_source.add('residual', (.1, .1, .8, .1), share_x='data')
    # or rely on them existing
    else:
         pass
    # do the plotting
    d = axes_source['data'].plot(data, **style)
    f = axes_source['data'].plot(fit, **{**style, alpha=.5})
    r = axes_source['residual'].plot(fit - data, **style)

    # return the artists in a structured way
    return {'data': d, 'fit': f, 'residual': r}

This lets you pass the same axes_source into multiple plotters and have them overlay nicely. It also provides a plausible path to customizing how the sub-layouts work (ex "I want my residual on top" vs "I want it on the bottom")

https://github.com/matplotlib/grid-strategy may be of interest.

I am a bit skeptical of mixing the ML logic into the same objects so you can delay the decision of what you want to plot until after you have done everything (but I could also imagine that viz.fit does more caching than model.fit?) ?

Is interactivity on the radar at all?

@jklymak
Copy link

jklymak commented Apr 26, 2019

Note that gridspecs can be nested, so your "axes" vis can still have multiple subplots inside it while participating in a larger axes layout in a figure. The nesting can be as many levels as you like:

https://matplotlib.org/gallery/subplots_axes_and_figures/gridspec_nested.html#sphx-glr-gallery-subplots-axes-and-figures-gridspec-nested-py

@bbengfort
Copy link
Member Author

@tacaswell and @jklymak thank you so much for your responses - very helpful information! Based on this I think that we would be happy to continue down the path of using AxesGrid and thinking of visualizers as being part of a larger figure. Also very happy to hear there is more documentation on the way, and if we can be of any assistance please let us know!

I will also consider the compatibility layer - I think Seaborn has something like this with FacetGrid? I do like the idea that the layout is something handled at a higher level rather than in each viz method - this will make things a lot easier for our contributors who want to focus on developing a specific visual tool. We could hook into a custom layout system in our finalize method if we had this intermediate object.

Interactivity is definitely on the radar - we've experimented some but need to dive deeper into it.

Thank you for the pointers to grid-strategy and the nested gridspec; we will definitely check those out!

@jklymak
Copy link

jklymak commented Apr 26, 2019

AxesGrid is not compatible with either tight_layout or constrained_layout so that is another drawback. I would tend to avoid its use unless very necessary. The only use case that I know that it is crucial for is when you want axes with equal aspect ratios to be close to each other on a figure that is too wide or too high. Other layout managers tend to spread the axes whereas axes grid puts the blank space on the sides.

@amueller
Copy link

amueller commented May 7, 2019

@jklymak can you elaborate on these limitations? GridSpec works with both of these, though, right?
There's two things I want from an interface:

  • being able to control a set of axes (say changing the markers on a 5x5 grid of scatterplots, and having a single legend for them).
  • putting something within existing axes. Say I have a plot that consists of 3 axes objects. Someone wants to put that into their already existing figure inside an existing axes.

@jklymak
Copy link

jklymak commented May 7, 2019

Yes, GridSpec works with both tight_layout and constrained_layout

The reason axes_grid1 doesn't work with these managers is because the location of plot elements is not determined until draw time. Sometimes that works OK, but its not guaranteed to work.

I don't see anything in your list of requirements that would require axes_grid1. Again, the use case where it really shines is replacing fig, axs = plt.subplots(2, 2, aspect='equal').

@amueller
Copy link

this seems also relevant: https://github.com/matplotlib/grid-strategy

@rebeccabilbro
Copy link
Member

this seems also relevant: https://github.com/matplotlib/grid-strategy

Indeed! Thanks @amueller!

@bbengfort
Copy link
Member Author

@rebeccabilbro I think this will require us to modify our matplotlib dependency to 2.0.2 or later.

@bbengfort
Copy link
Member Author

In #957 we added a Visualizer.fig and updated the dependency to 2.0.2 or later. These changes along with this excellent discussion has moved us forward to our v1.0 release and v1.1 will include more robust support for multi-Axes figures and quick methods.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
level: expert deep knowledge of packages required priority: high should be done before next release type: question more information is required
Projects
None yet
Development

No branches or pull requests

5 participants