Thanks to visit codestin.com
Credit goes to github.com

Skip to content

searborn adaptation need for Matptotlib-2.2.0rc1 #10585

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

Closed
stonebig opened this issue Feb 24, 2018 · 37 comments
Closed

searborn adaptation need for Matptotlib-2.2.0rc1 #10585

stonebig opened this issue Feb 24, 2018 · 37 comments

Comments

@stonebig
Copy link
Contributor

Bug report

Bug summary

My historic Seaborn example doesn't seem to work anymore with Matplotlib-2.2.0rc1. is it a volontary change ?

# Seaborn
# for more examples, see http://stanford.edu/~mwaskom/software/seaborn/examples/index.html
import seaborn as sns
sns.set()
df = sns.load_dataset("iris")
sns.pairplot(df, hue="species", size=1.5)

Actual outcome

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-9-78e2f3db95aa> in <module>()
      4 sns.set()
      5 df = sns.load_dataset("iris")
----> 6 sns.pairplot(df, hue="species", size=1.5)

C:\WinPython\bd35\buildQt5\winpython-32bit-3.5.x.2\python-3.5.4\lib\site-packages\seaborn\axisgrid.py in pairplot(data, hue, hue_order, palette, vars, x_vars, y_vars, kind, diag_kind, markers, size, aspect, dropna, plot_kws, diag_kws, grid_kws)
   2058     if grid.square_grid:
   2059         if diag_kind == "hist":
-> 2060             grid.map_diag(plt.hist, **diag_kws)
   2061         elif diag_kind == "kde":
   2062             diag_kws["legend"] = False

C:\WinPython\bd35\buildQt5\winpython-32bit-3.5.x.2\python-3.5.4\lib\site-packages\seaborn\axisgrid.py in map_diag(self, func, **kwargs)
   1333                     diag_ax = ax._make_twin_axes(sharex=ax,
   1334                                                  sharey=diag_axes[0],
-> 1335                                                  frameon=False)
   1336                 else:
   1337                     diag_ax = ax._make_twin_axes(sharex=ax, frameon=False)

C:\WinPython\bd35\buildQt5\winpython-32bit-3.5.x.2\python-3.5.4\lib\site-packages\matplotlib\axes\_subplots.py in _make_twin_axes(self, *kl, **kwargs)
    174         from matplotlib.projections import process_projection_requirements
    175         if 'sharex' in kwargs and 'sharey' in kwargs:
--> 176             raise ValueError("Twinned Axes may share only one axis.")
    177         kl = (self.get_subplotspec(),) + kl
    178         projection_class, kwargs, key = process_projection_requirements(

ValueError: Twinned Axes may share only one axis.

Matplotlib version

  • Operating system: windows10
  • Matplotlib version: 2.2.0rc1 of feb 18th cgohlke
  • Matplotlib backend (print(matplotlib.get_backend())):module://ipykernel.pylab.backend_inline
  • Python version: 3.5.4
  • Jupyter version (if applicable): Jupyterlab-0.31.8
  • Other libraries:
@stonebig
Copy link
Contributor Author

stonebig commented Feb 24, 2018

so it comes from this change:
#10033

how should we rewrite an old code like :

ax._make_twin_axes(sharex=ax,
 sharey=diag_axes[0],
 frameon=False)

is it ?:

ax._make_twin_axes(# don't ax me twice sharex=ax,
 sharey=diag_axes[0],
 frameon=False)

@stonebig stonebig changed the title searborn failure with Matptotlib-2.2.0rc1 ? searborn adaptation need for Matptotlib-2.2.0rc1 Feb 24, 2018
@tacaswell
Copy link
Member

attn @efiring

I think was was intentional to account for interactions between twin, shared axes, and fixed aspect ratio.

@stonebig
Copy link
Contributor Author

stonebig commented Feb 24, 2018

ok, I patched seaborn for my need. seaborn maintainer ask if this internal breakage could be avoided:
mwaskom/seaborn#1378 (comment)

@jklymak
Copy link
Member

jklymak commented Feb 24, 2018

Can you distill to a non-seaborn example? I’m still not sure what seaborn is trying to do that breaks, but I haven’t read the source.

@stonebig
Copy link
Contributor Author

stonebig commented Feb 24, 2018

Similar examples would come from holoviews (replace 'bokeh' per 'matplotlib'):

2axes_holoviews

@stonebig
Copy link
Contributor Author

stonebig commented Feb 24, 2018

more similar would be with hv.Histogram, yet more verbose than compact seaborn

from bokeh.sampledata.iris import flowers
from holoviews.operation import gridmatrix
ds = hv.Dataset(flowers)
grouped_by_species = ds.groupby('species', container_type=hv.NdOverlay)
grid = gridmatrix(grouped_by_species, diagonal_type=hv.Histogram)

@jklymak
Copy link
Member

jklymak commented Feb 24, 2018

I meant can you trigger the error using matplotlib only?

@stonebig
Copy link
Contributor Author

hum, it's not an internal error of Matplotlib-2.2.0rc1, if that is your question.

@jklymak
Copy link
Member

jklymak commented Feb 24, 2018

So I'm confused; is this a matplotlib problem (i.e. something that worked pre #10033)? If so, then it must be triggerable with just matplotlib calls.

@stonebig
Copy link
Contributor Author

stonebig commented Feb 25, 2018

seaborn was using an "internal API", functions starting per the undescore characters:

  • there has been a change there,
  • and seaborn is no longer actively maintained.

@efiring
Copy link
Member

efiring commented Feb 25, 2018

The change in _make_twin_axes was deliberate. Under what circumstances does it make sense to share both axes with twin axes? The point of twin axes is to share one axis and have the other one differ.

@stonebig
Copy link
Contributor Author

ok. I agree it was probably a functional bug of seaborn.

@mwaskom
Copy link

mwaskom commented Feb 27, 2018

@efiring

The change in _make_twin_axes was deliberate. Under what circumstances does it make sense to share both axes with twin axes? The point of twin axes is to share one axis and have the other one differ.

seaborn shouldn't have been using a private method here, but if the issue is just redundancy, is there any reason matplotlib needs to raise on getting sharex and sharey when one of the shared axes is self?

@mwaskom
Copy link

mwaskom commented Feb 27, 2018

Here's a self-contained example of what works on 2.1.1 and I gather fails on 2.2:

f, (ax0, ax1) = plt.subplots(1, 2, sharey=True)
ax2 = ax1._make_twin_axes(sharex=ax1, sharey=ax0)

@mwaskom
Copy link

mwaskom commented Feb 27, 2018

BTW the reason seaborn was using the private method is that I don't believe it is possible through the public API to create a shared twinned axes (at least, ax.twinx has no parameters that would allow this, maybe there is another method).

@efiring
Copy link
Member

efiring commented Feb 27, 2018

I don't yet understand what you are trying to do in that example. It looks like ax1 and ax2, the twins with their shared x-axis, are being forced to have the same y-axis as well, via the sharing with ax0. In that case, what is the need for having both ax1 and ax2? Their x axes are identical, and their y axes are identical, so why do you need two Axes?

Adding the check to _make_twin_axes was part of an attempt to prevent over-specification or inconsistent specifications of sharing, twinning (which locks the two position boxes together) and aspect-ratio control. I'm sure it is still not optimal.

@mwaskom
Copy link

mwaskom commented Feb 27, 2018

I don't yet understand what you are trying to do in that example. It looks like ax1 and ax2, the twins with their shared x-axis, are being forced to have the same y-axis as well, via the sharing with ax0. So why do you need two Axes?

The actual use case is a bit more complicated. The goal is to make a home for the for the histograms on the diagonal axes of a seaborn PairGrid (aka a scatterplot matrix). The point is that the grid is set up so that x axes are shared across rows and y axes are shared across columns. The histograms are drawn on twins of the diagonal axes so that they share x axes with the rest of the plots in each column, but their y axes are shared across the diagonal to show the counts on a common scale that is otherwise distinct from the scale that is shared across each row. Does that make sense?

Adding the check to _make_twin_axes was part of an attempt to prevent over-specification or inconsistent specifications of sharing, twinning (which locks the two position boxes together) and aspect-ratio control. I'm sure it is still not optimal.

As I see it, the error here is a false positive because ax2 really does share one axis with its twin. It's raised because sharex is explicitly specified where otherwise you'd get the same result implicitly (explicit is better than implicit). I think it should be easy to modify the checked condition not to raise if both sharex and sharey are passed but one of them is self.

Granted, this is a private API, so you can feel free to change it at will, but if an easy fix could avoid completely breaking a popular seaborn function, that would be rad.

@jklymak
Copy link
Member

jklymak commented Feb 27, 2018

If this can't be achieved w/ the public API, then we should develop a public API to do it, and have tests etc.

@mwaskom can you make a 2x2 (or 3x3) toy example that shows the full use case? It seems like a useful feature.

@jklymak
Copy link
Member

jklymak commented Feb 27, 2018

When you create an axes, you can set its sharex and sharey kwargs. I don't think it'd be unreasonable to make that a public API of an axes object....

For now, I think you can do:

ax._shared_x_axes.join(ax, masterxaxes)
ax._shared_y_axes.join(ax, masteryaxes)

I think that is closer to what is wanted, rather than twinning the axes, but maybe I'm misunderstanding..

@mwaskom
Copy link

mwaskom commented Feb 27, 2018

If this can't be achieved w/ the public API, then we should develop a public API to do it, and have tests etc.

I agree. But in the meantime, this change will break one of the most basic seaborn examples, so I'd really also like to advocate for a minor change to the work in #10033 that won't, as far as I understand, undermine any of its actual goals or otherwise allow incorrect specifications.

@mwaskom can you make a 2x2 (or 3x3) toy example that shows the full use case? It seems like a useful feature.

This is basically what happens in seaborn:

f, axes = plt.subplots(2, 2, figsize=(6, 6), sharex="col", sharey="row")
diag_axes = []
for ax in np.diag(axes):
    if diag_axes:
        diag_ax = ax._make_twin_axes(sharex=ax,
                                     sharey=diag_axes[0])
    else:
        diag_ax = ax._make_twin_axes(sharex=ax)
    diag_ax.set_axis_off()
    diag_axes.append(diag_ax)

@mwaskom
Copy link

mwaskom commented Feb 27, 2018

I think that is closer to what is wanted, rather than twinning the axes, but maybe I'm misunderstanding..

@jklymak Really the simplest thing would be an ax.twinx signature that looks like def twinx(self, sharey=None), and likewise for ax.twiny, but I don't think implementing that will be possible until the change I'm advocating for in the private method is implemented, because otherwise you'll end up with exactly the same error that seaborn is triggering.

Maybe I'm completely missing something about the goal of #10033 though...

@mwaskom
Copy link

mwaskom commented Feb 27, 2018

Please let me know if, at least, the usecase is clear, because I really think it's quite straightforward and perhaps being overthought.

@jklymak
Copy link
Member

jklymak commented Feb 27, 2018

Thanks very much for the clear example.

OTOH, I'm not personally in favour of accomodating this. As @efiring said, seaborn over-specified the sharex argument, for no good reason as far as I can tell. Sharing axes is not without side effects, and telling the twin method that we want to share both x and y doesn't make sense. Being explicit just means we have to add extra code to see if you are being explicit. That it worked before caused other problems, that we've specifically adressed by making this change.

We could put a janky test in _make_twin_axes to see if one of the specified axes is self and allow passing both sharex and sharey, but it really seems this should be patched in seaborn by just dropping the sharex kwargs.

On a larger scale, it seems twinning is the wrong thing to do altogether. If I understand correctly, you don't want a twin, you really want the diagonal axes to have different sharex and sharey than column and row. I think we should/could impliment that. i.e. ax.sharex(listofaxes) and ax.sharey(listofaxes).

Just my opinion. I'm sure a PR to put the check you want would be evenhandedly evaluated.

@jklymak
Copy link
Member

jklymak commented Feb 27, 2018

BTW, I'll re-open just in way of getting on more people's radars...

I'll also milestone, because if we are going to change something, it should happen before 2.2, but feel free to un-milestone if we don't think this is going anywhere.

@jklymak jklymak reopened this Feb 27, 2018
@jklymak jklymak added this to the v2.2.0 milestone Feb 27, 2018
@mwaskom
Copy link

mwaskom commented Feb 27, 2018

OTOH, I'm not personally in favour of accomodating this. As @efiring said, seaborn over-specified the sharex argument, for no good reason as far as I can tell.

The reason is mainly because that's exactly what ax.twinx does! Hence my point about eliminating this error being necessary anyway to having a public API that does this, at least through the _make_twin_axes route.

@mwaskom
Copy link

mwaskom commented Feb 27, 2018

We could put a janky test in _make_twin_axes to see if one of the specified axes is self and allow passing both sharex and sharey, but it really seems this should be patched in seaborn by just dropping the sharex kwargs.

Maybe, maybe not. Given that I'm copying what matplotlib does, I suspect there may be a good reason for it. And in any case, if 2.2 lands with this it will break all existing versions of seaborn with no way to work around the problem. And the "do a scatterplot matrix" is often the first one-liner people use to introduce seaborn, so that's going to mean problems for a lot of people.

On a larger scale, it seems twinning is the wrong thing to do altogether. If I understand correctly, you don't want a twin, you really want the diagonal axes to have different sharex and sharey than column and row. I think we should/could impliment that. i.e. ax.sharex(listofaxes) and ax.sharey(listofaxes).

seaborn actually does require twin axes because the upper-left hand axes has to have the correct ticks for the other axes in that row. And also it's possible to use the PairGrid without having a different axes system for the diagonal plots, and the object doesn't know at the time the original grid of plots is created whether the user is or is not going to draw marginal distributions in the diagonal.

@dopplershift
Copy link
Contributor

dopplershift commented Feb 27, 2018

Speaking as a matplotlib dev with no attachment to the APIs in question, I'm sensitive to the idea that we not break one of the more prominent downstream libraries. So I'm ok with getting in a change to avoid this breakage before we cut 2.2.

I'm also pretty damned irritated that we have to work around this prominent library using an internal API; especially since it seems that this downstream library has made no effort whatsoever in getting a public version of this API. (Please correct me if I'm wrong on this.) So I'm also tempted to say that unless a seaborn dev is willing to put some skin in the game and propose a PR to get a public API that serves their purpose, we don't do a thing. THIS is literally the reason that as a developer of another project, YOU DON'T USE SOMEONE ELSE'S INTERNAL APIS.

@jklymak
Copy link
Member

jklymak commented Feb 27, 2018

OK, fair enough - tracing through all the twin code is difficult. I did reopen and remilestone so this will at least get discussion before 2.2 drops.

  1. I'm still not clear why you want twin axes versus just clearing the diagonal axes and resetting the share properties. But no doubt I'm not following exactly what PairGrid etc are doing.
  2. Again, the changes implimented in Improve handling of shared axes with specified aspect ratio #10033 fix a lot of other critical bugs. So, if you can figure out how to fix seaborn w/o breaking Improve handling of shared axes with specified aspect ratio #10033, I think that PR would be great.

EDIT: Ooops, crossing comments....

@mwaskom
Copy link

mwaskom commented Feb 27, 2018

Thanks @dopplershift for the constructive contributions.

@mwaskom
Copy link

mwaskom commented Feb 27, 2018

My proposal is literally to change

if 'sharex' in kwargs and 'sharey' in kwargs:
    raise ValueError("Twinned Axes may share only one axis.")

to

if 'sharex' in kwargs and 'sharey' in kwargs:
    if kwargs["sharex"] is not self and kwargs["sharey"] is not self:
        raise ValueError("Twinned Axes may share only one axis.")

@mwaskom
Copy link

mwaskom commented Feb 27, 2018

We can also have a conversation about a public API for twinned, shared axes (which I've proposed above), but that shouldn't be a blocker for 2.2. And, as noted, fixing this error check would be a precondition for making that possible.

stonebig added a commit to stonebig/matplotlib that referenced this issue Feb 27, 2018
@dstansby
Copy link
Member

@mwaskom is it not possible write (or just copy the old code in _make_twin_axes) a method to do what you want to do instead of relying on our private one, and then putting out a patch release of seaborn with that fix?

@mwaskom
Copy link

mwaskom commented Feb 27, 2018

No.

@dstansby
Copy link
Member

Why not?

@dopplershift
Copy link
Contributor

That doesn't solve the problem of all the existing installs being broken.

@jklymak
Copy link
Member

jklymak commented Feb 28, 2018

Note that @lkjell took a crack at adding share/unshare in #9923. I think that is what would be useful for the seaborn use case...

@jklymak
Copy link
Member

jklymak commented Feb 28, 2018

Closed #10622

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants