seaborn
seaborn copied to clipboard
so.Est() + tick label formatter => 'PseudoAxis' object has no attribute '_view_interval'
Hi, thanks for cool library, I especially enjoy using objects interface.
While using it I encountered a small issue when trying to customise tick labels using matplotlib formatter in combination with so.Est()
. If I replace it with so.Agg()
the issue disappears. I guess this has something to do with the fact that several variables use the same scale (y/ymin/ymax) and _view_interval
is not initialised properly in that case?
import seaborn as sns
import seaborn.objects as so
import matplotlib as mpl
fmri = sns.load_dataset("fmri")
(
so.Plot(fmri, x="timepoint", y="signal", color="event")
.add(so.Band(), so.Est())
.scale(y=so.Continuous().label(mpl.ticker.PercentFormatter()))
)
File ~/.pyenv/versions/miniforge3/lib/python3.9/site-packages/matplotlib/ticker.py:233, in Formatter.format_ticks(self, values)
231 """Return the tick labels for all the ticks at once."""
232 self.set_locs(values)
--> 233 return [self(value, i) for i, value in enumerate(values)]
File ~/.pyenv/versions/miniforge3/lib/python3.9/site-packages/matplotlib/ticker.py:233, in <listcomp>(.0)
231 """Return the tick labels for all the ticks at once."""
232 self.set_locs(values)
--> 233 return [self(value, i) for i, value in enumerate(values)]
File ~/.pyenv/versions/miniforge3/lib/python3.9/site-packages/matplotlib/ticker.py:1525, in PercentFormatter.__call__(self, x, pos)
1523 def __call__(self, x, pos=None):
1524 """Format the tick as a percentage with the appropriate scaling."""
-> 1525 ax_min, ax_max = self.axis.get_view_interval()
1526 display_range = abs(ax_max - ax_min)
1527 return self.fix_minus(self.format_pct(x, display_range))
File ~/.pyenv/versions/miniforge3/lib/python3.9/site-packages/seaborn/_core/scales.py:921, in PseudoAxis.get_view_interval(self)
920 def get_view_interval(self):
--> 921 return self._view_interval
AttributeError: 'PseudoAxis' object has no attribute '_view_interval'
Versions: seaborn: 0.13.0 matplotlib: 3.6.2
Thanks for the reproducible example! I don't know exactly what is causing this but I think you're on the right track with your suggestion. Note that it is also possible to get percent formatting by directly using the seaborn API:
fmri = sns.load_dataset("fmri")
(
so.Plot(fmri, x="timepoint", y="signal", color="event")
.add(so.Band(), so.Est())
.scale(y=so.Continuous().label(like="{x:.1%}"))
)
A similar (but different) error is apparent when trying to use a LogLocator
for ticking of bands added with so.Est
:
import seaborn as sns
import seaborn.objects as so
import matplotlib as mpl
fmri = sns.load_dataset("fmri")
(
so.Plot(fmri, x="timepoint", y="signal", color="event")
.add(so.Band(), so.Est())
.scale(y=so.Continuous().tick(mpl.ticker.LogLocator()))
)
Which errors the following AttributeError
(mpl 3.8.2, sns 0.13.0):
AttributeError Traceback (most recent call last)
File ~\miniconda3\envs\tst\Lib\site-packages\IPython\core\formatters.py:344, in BaseFormatter.__call__(self, obj)
342 method = get_real_method(obj, self.print_method)
343 if method is not None:
--> 344 return method()
345 return None
346 else:
File ~\miniconda3\envs\tst\Lib\site-packages\seaborn\_core\plot.py:387, in Plot._repr_png_(self)
385 if Plot.config.display["format"] != "png":
386 return None
--> 387 return self.plot()._repr_png_()
File ~\miniconda3\envs\tst\Lib\site-packages\seaborn\_core\plot.py:934, in Plot.plot(self, pyplot)
930 """
931 Compile the plot spec and return the Plotter object.
932 """
933 with theme_context(self._theme_with_defaults()):
--> 934 return self._plot(pyplot)
File ~\miniconda3\envs\tst\Lib\site-packages\seaborn\_core\plot.py:964, in Plot._plot(self, pyplot)
962 # Process the data for each layer and add matplotlib artists
963 for layer in layers:
--> 964 plotter._plot_layer(self, layer)
966 # Add various figure decorations
967 plotter._make_legend(self)
File ~\miniconda3\envs\tst\Lib\site-packages\seaborn\_core\plot.py:1505, in Plotter._plot_layer(self, p, layer)
1503 # TODO is this the right place for this?
1504 for view in self._subplots:
-> 1505 view["ax"].autoscale_view()
1507 if layer["legend"]:
1508 self._update_legend_contents(p, mark, data, scales, layer["label"])
File ~\miniconda3\envs\tst\Lib\site-packages\matplotlib\axes\_base.py:2939, in _AxesBase.autoscale_view(self, tight, scalex, scaley)
2934 # End of definition of internal function 'handle_single_axis'.
2936 handle_single_axis(
2937 scalex, self._shared_axes["x"], 'x', self.xaxis, self._xmargin,
2938 x_stickies, self.set_xbound)
-> 2939 handle_single_axis(
2940 scaley, self._shared_axes["y"], 'y', self.yaxis, self._ymargin,
2941 y_stickies, self.set_ybound)
File ~\miniconda3\envs\tst\Lib\site-packages\matplotlib\axes\_base.py:2896, in _AxesBase.autoscale_view.<locals>.handle_single_axis(scale, shared_axes, name, axis, margin, stickies, set_bound)
2894 # If x0 and x1 are nonfinite, get default limits from the locator.
2895 locator = axis.get_major_locator()
-> 2896 x0, x1 = locator.nonsingular(x0, x1)
2897 # Find the minimum minpos for use in the margin calculation.
2898 minimum_minpos = min(
2899 getattr(ax.dataLim, f"minpos{name}") for ax in shared)
File ~\miniconda3\envs\tst\Lib\site-packages\matplotlib\ticker.py:2427, in LogLocator.nonsingular(self, vmin, vmax)
2424 vmin, vmax = 1, 10
2425 else:
2426 # Consider shared axises
-> 2427 minpos = min(axis.get_minpos() for axis in self.axis._get_shared_axis())
2428 if not np.isfinite(minpos):
2429 minpos = 1e-300 # This should never take effect.
AttributeError: 'PseudoAxis' object has no attribute '_get_shared_axis'
Probably the same underlying problem but @MaozGelbart I am curious what your usecase is for using a LogLocator
on a linear-scale axis?
Probably the same underlying problem but @MaozGelbart I am curious what your usecase is for using a
LogLocator
on a linear-scale axis?
No such use case, just for ease of reproduction. Can reproduce with this code as well:
import seaborn as sns
import seaborn.objects as so
import matplotlib as mpl
fmri = sns.load_dataset("fmri")
(
so.Plot(fmri, x="timepoint", y="signal", color="event")
.add(so.Band(), so.Est())
.scale(y=so.Continuous(trans="symlog").tick(locator=LogLocator()))
)
OK got it, thanks!
(I guess, follow-up question, did you have a usecase where LogLocator
was necessary to configure something that couldn't be configured through the other parameters to Continuous.tick
? Or were you just poking at the issue?)
(I guess, follow-up question, did you have a usecase where
LogLocator
was necessary to configure something that couldn't be configured through the other parameters toContinuous.tick
? Or were you just poking at the issue?)
In the objects interface symlog scale uses a different locator than log scale, so sometimes in this scenario it makes sense to change the locator into a logarithmic one.