You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Scatter autoscaling still has issues with log scaling and zero values #16552
I installed the 3.2.0rc3 package, and I can confirm that this fixes almost all the issues with autoscaling and scatter. However, if you have points with 0 for the y-axis and enable log scaling there is still an issue with the scatter plot auto scaling (that doesn't show up with the standard plot function).
@JadinLuong yes, this is suitable as a first issue.
First steps:
Run the above code example and verify that plot() yields a reasonable result while scatter() does not. - Actually what does it do with the 0 values? Is that reasonable?
Dig into the functions and find out why they behave differently.
Write a test for scatter() that checks the desired scaling behavior and thus currently fails.
Adapt scatter() so that it behaves like plot() with respect to scaling and now passes the above test.
Create ar PR with your code.
If you have technical trouble or want to learn the development workflow and conventions for Matplotlib, check the developers guide. Since this is quite lengthy, you may also ask if you have a particular question.
The problem here is that plot adds a Line2D, whereas scatter adds a PathCollection. In the former case, the Axes.dataLim is updated in Axes.add_line using Line2D.get_path(). In the latter case, the Axes.dataLim is updated in Axes.add_collection using Collection.get_datalim().
Updating using the Path means it will check every single point, while updating using the collection's data limits means it will only check the minimum x/y and the maximum x/y. This actually results in the same data limits, but on a log scale, the y=0 point will be ignored, so it produces a different minposy. In the latter case, there are only two points, so minposy is actually set to the maximum y.
On log scales, any view limit <= 0 is set to minposy. For plot, that's just the next value up (0.1), but for scatter, it's the maximum (71.3). You only see something in the end because the nonsingular check sees that vmin==vmax and picks 'nice' values around it.
To fix this, there would need to be some change in add_collection to actually set the right minpos? values. For scatter, it could grab the actual path and update dataLim using it, but I don't think that's true of all collections, and a bit of a hack fix.
Collection.get_datalim returns a Bbox, so we do have somewhere to store minpos? without complicated API changes. However, Axes.add_collection converts that into a Path to update the dataLim, throwing away any of that.
So this would need some coordination to say that Collection.get_datalim should fill in minpos? and that Bbox.update_from_data_xy (or probably a new function) should merge that information.
This is perhaps not difficult, but might require some carefulness and thorough testing, so I'm not too sure it'll be fixed for 3.2.2.
Surprisingly, this works and does not break any tests:
diff --git a/lib/matplotlib/collections.py b/lib/matplotlib/collections.py
index 51c6c50a03..e785ceb462 100644
--- a/lib/matplotlib/collections.py+++ b/lib/matplotlib/collections.py@@ -290,9 +290,7 @@ class Collection(artist.Artist, cm.ScalarMappable):
# note A-B means A B^{-1}
offsets = np.ma.masked_invalid(offsets)
if not offsets.mask.all():
- points = np.row_stack((offsets.min(axis=0),- offsets.max(axis=0)))- return transforms.Bbox(points)+ return offsets
return transforms.Bbox.null()
def get_window_extent(self, renderer):
It just happens to work because Axes.update_datalim uses Bbox.update_from_data_xy I mentioned, and Axes.add_collection passes the above result directly to it. However, it's not really a great patch, as then Collection.get_datalim sometimes returns an array of points, and sometimes a bbox. And it doesn't handle the other cases in Collection.get_datalim.
If we added a second method that returned the points, while Collection.get_datalim returned the bbox from it, then we could have add_collection call this new method and update the Axes dataLim with the full data.
Activity
JadinLuong commentedon Mar 4, 2020
Hey, I'm new to matplotlib and I am willing to contribute. Would this be a good first issue? If so what are some first steps in tackling this issue?
timhoffm commentedon Mar 6, 2020
@JadinLuong yes, this is suitable as a first issue.
First steps:
plot()yields a reasonable result whilescatter()does not. - Actually what does it do with the 0 values? Is that reasonable?scatter()that checks the desired scaling behavior and thus currently fails.scatter()so that it behaves likeplot()with respect to scaling and now passes the above test.If you have technical trouble or want to learn the development workflow and conventions for Matplotlib, check the developers guide. Since this is quite lengthy, you may also ask if you have a particular question.
Added unit tests for bug fix for matplotlib#16552
Fixed missing headers in tests for bug fix for matplotlib#16552
QuLogic commentedon May 9, 2020
The problem here is that
plotadds aLine2D, whereasscatteradds aPathCollection. In the former case, theAxes.dataLimis updated inAxes.add_lineusingLine2D.get_path(). In the latter case, theAxes.dataLimis updated inAxes.add_collectionusingCollection.get_datalim().Updating using the
Pathmeans it will check every single point, while updating using the collection's data limits means it will only check the minimum x/y and the maximum x/y. This actually results in the same data limits, but on a log scale, the y=0 point will be ignored, so it produces a differentminposy. In the latter case, there are only two points, sominposyis actually set to the maximum y.On log scales, any view limit <= 0 is set to
minposy. Forplot, that's just the next value up (0.1), but forscatter, it's the maximum (71.3). You only see something in the end because the nonsingular check sees that vmin==vmax and picks 'nice' values around it.To fix this, there would need to be some change in
add_collectionto actually set the rightminpos?values. Forscatter, it could grab the actual path and updatedataLimusing it, but I don't think that's true of all collections, and a bit of a hack fix.QuLogic commentedon May 9, 2020
Collection.get_datalimreturns aBbox, so we do have somewhere to storeminpos?without complicated API changes. However,Axes.add_collectionconverts that into aPathto update thedataLim, throwing away any of that.So this would need some coordination to say that
Collection.get_datalimshould fill inminpos?and thatBbox.update_from_data_xy(or probably a new function) should merge that information.This is perhaps not difficult, but might require some carefulness and thorough testing, so I'm not too sure it'll be fixed for 3.2.2.
QuLogic commentedon Oct 3, 2020
Surprisingly, this works and does not break any tests:
It just happens to work because
Axes.update_datalimusesBbox.update_from_data_xyI mentioned, andAxes.add_collectionpasses the above result directly to it. However, it's not really a great patch, as thenCollection.get_datalimsometimes returns an array of points, and sometimes a bbox. And it doesn't handle the other cases inCollection.get_datalim.If we added a second method that returned the points, while
Collection.get_datalimreturned the bbox from it, then we could haveadd_collectioncall this new method and update the Axes dataLim with the full data.Add a test for scatter autolim on log scale.
Add a test for scatter autolim on log scale.
Add a test for scatter autolim on log scale.
Add a test for scatter autolim on log scale.
petor-traffs commentedon Dec 2, 2020
Hello!! Is this issue still open? I'd like to try it!
dopplershift commentedon Dec 4, 2020
Looks like #18642 has been opened to try to fix this one. Might not be a good starting point.