Skip to content

[NF] Add 'truncate' and 'join' methods to colormaps. #7716

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
wants to merge 18 commits into from

Conversation

lkilcher
Copy link
Contributor

This is functionality that I've been wanting built-in for a while. The inspiration largely comes from here and here. I know @tacaswell had asked that this functionality be included way back in 2014.

I think there is still an 'issue' here if you have/want a discontinuous LinearSegmentedColormap because this implementation just smooths those over. Still, if N is high, that shouldn't be too much of a problem. I've developed a version of the LinearSegmentedColormap.truncate method that actually crops the cdict appropriately, but since MEP21 seems to indicate that MPL may be moving away from LSC's, I didn't bother including it. Plus, it's a bit tricky to create a join method that correctly joins LSC's to listed cmaps.

Suggestions welcome!

The fraction of the new colormap that should be occupied
by self. By default, this is ``self.N / (self.N +
other.N)``.

Copy link
Member

Choose a reason for hiding this comment

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

What does the N kwarg do?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

OK. I've updated the docstring.

@codecov-io
Copy link

codecov-io commented Dec 31, 2016

Current coverage is 62.10% (diff: 100%)

Merging #7716 into master will decrease coverage by 4.48%

@@             master      #7716   diff @@
==========================================
  Files           109        174     +65   
  Lines         46648      56062   +9414   
  Methods           0          0           
  Messages          0          0           
  Branches          0          0           
==========================================
+ Hits          31061      34815   +3754   
- Misses        15587      21247   +5660   
  Partials          0          0           

Powered by Codecov. Last update 07f6427...615ca22

@tacaswell tacaswell added this to the 2.1 (next point release) milestone Dec 31, 2016
cmap_trunc = cmap.truncate(0.2, 0.7)

"""
assert minval < maxval, "minval must be less than maxval"
Copy link
Member

Choose a reason for hiding this comment

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

Can you use if not cond: raise instead of asserts? A while ago we went through and removed all of the run-time uses of asserts for input validation as (in principle) the interpreter can decide to ignore the assert statements.

@@ -742,6 +742,107 @@ def func_r(x):

return LinearSegmentedColormap(name, data_r, self.N, self._gamma)

def join(self, other, name=None, frac_self=None, N=None):
Copy link
Member

Choose a reason for hiding this comment

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

It might be a good idea to also add add a __add__ method so you can do cm1 + cm2 and get a 50/50 map? Could be too cute for it's own good.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I had this idea as well, but thought reviewers would find it 'too cute'! It's included now.

@@ -742,6 +742,107 @@ def func_r(x):

return LinearSegmentedColormap(name, data_r, self.N, self._gamma)

def join(self, other, name=None, frac_self=None, N=None):
Copy link
Member

Choose a reason for hiding this comment

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

Maybe put a generic version of this (using ListedColormap in all case on Colormap? Might also make sense to have a generic join_color_map(*cmaps, fractions=None) as a top-level function (rather than as methods)?

@tacaswell
Copy link
Member

👍 , left some comments about the API

This will definitely need a whats_new entry.

@lkilcher
Copy link
Contributor Author

lkilcher commented Jan 1, 2017

Most of the suggested changes have been made. A few things:

  1. Does the whats_new entry go into doc/api/api_changes.rst?
  2. Any suggestions on where to put the join_colormaps function?
  3. I had to remake the image tests when I switched to returning only ListedColormap. We probably don't need the lsc test images anymore. Agree?

@lkilcher
Copy link
Contributor Author

lkilcher commented Jan 1, 2017

After adding the __add__ method, it got me thinking about how to do something similar for truncate. Since these ideas are a bit half-baked, I started a new branch. Thoughts on this?

@lkilcher
Copy link
Contributor Author

@tacaswell I'm sure you're busy. I just wanted to make sure this wasn't forgotten. Any thoughts on my questions above?

@WeatherGod
Copy link
Member

WeatherGod commented Jan 31, 2017 via email

@lkilcher
Copy link
Contributor Author

lkilcher commented Feb 2, 2017

Can anyone help me understand what is breaking here?

@dstansby
Copy link
Member

dstansby commented Feb 2, 2017

It looks like it's just PEP8 violations, for lines that are longer than 79 characters. You should be able to see which lines are causing the error here: https://travis-ci.org/matplotlib/matplotlib/jobs/197464743#L1767

@lkilcher
Copy link
Contributor Author

Not sure if there is bandwidth to include this in 2.1, but I'm available to work on it. I think I've addressed all existing comments, except for the whats_new entry. Where do I put that?

Also, I've addressed @WeatherGod 's comments on the proposed __getitem__ method. I'll plan to merge that into this soon, unless there are objections?

@tacaswell tacaswell modified the milestones: 2.1 (next point release), 2.2 (next next feature release) Aug 29, 2017
Copy link
Member

@jklymak jklymak left a comment

Choose a reason for hiding this comment

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

This looks interesting and helpful. Needs to pass the tests. Not sure if I think we need an image test.

plt.pcolormesh(data, cmap=cmap)
plt.colorbar(orientation='vertical')


Copy link
Member

Choose a reason for hiding this comment

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

Not sure this requires image comparisons (which are repo-heavy). Can you just test on the returned colormaps?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hi Jody! I hadn't thought of this, but it makes sense. Are the colormaps sitting somewhere that I can compare to, or... ?

Copy link
Member

Choose a reason for hiding this comment

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

Hi Levi, I'd just evaluate the colormap at 5 or 6 locations and assert that the values returned are what you say they should be. That seems trivial but if someone mucks with your code they will have to also change the test and thats a good warning to double check what was done.

@jklymak jklymak modified the milestones: needs sorting, v3.0 May 17, 2018
@lkilcher
Copy link
Contributor Author

OK. I think this is ready to go. Let me know if there is anything else you need.

@jklymak jklymak self-assigned this May 28, 2018
@tacaswell tacaswell modified the milestones: v3.0, v3.1 Jul 9, 2018
Copy link
Member

@jklymak jklymak left a comment

Choose a reason for hiding this comment

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

This looks really useful to me. Not 100% sure about the format of the getitem string, and I'm not even sure where this would render in the docs. I also wish this had an actual example somewhere so folks will know about it, otherwise, I think the only documentation is in the whats-new. OTOH that could be done for a second PR...


# ### float indexing
# for float-style indexing, the values must be in [0.0, 1.0]
# Truncate the colormap between 20 and 80%.
Copy link
Member

Choose a reason for hiding this comment

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

Typo? 80->60?

@jklymak jklymak changed the title [WIP] [NF] Add 'truncate' and 'join' methods to colormaps. [NF] Add 'truncate' and 'join' methods to colormaps. Aug 19, 2018
@jklymak
Copy link
Member

jklymak commented Aug 20, 2018

At the weekly call this week this got substantial discussion. The upshot was that this seems a lot of maintenence, support, and API overhead for something that the user should be able to do themselves, perhaps with better documentation. Basically truncating and subsetting colormaps boils down to making the appropriate matrix to feed to ListedColormap via whatever tools the user is comfortable using in numpy to achieve that end. The major disadvantage of doing it the proposed way, was felt to be the ambiguity of colormap length, and hence the diffculting of knowing whether the join point of two concatenated colormaps was in the middle, or at 1/3 etc. If people have to explicitly specify the length of the colormap, they are most of the way to just manipulating the arrays directly.

The Todo here is to add a section to https://matplotlib.org/tutorials/colors/colormaps.html#sphx-glr-tutorials-colors-colormaps-py that explains colormap manipulation better, and perhaps adding an example or two to the example directory.

@lkilcher thanks so much for the contribution, and sorry it languished so long. If you want to give input to the examples when those PRs come in, I'll ping you, or contributions along those lines would be most welcome.

@lkilcher
Copy link
Contributor Author

This is disappointing, and it's too bad this conversation didn't happen before I put time into the tests etc.. I can understand that __getitem__ is more than the maintainers wanted to handle, but .join and truncate are so necessary! God knows how much time I've spent digging into the code to 're-remember' how to join/truncate colormaps (and it's been changing)! If people are worried about N ambiguity, make that a required argument of the functions.

Making a clearly documented interface to the arrays makes sense, and certainly replaces the need for __getitem__, but I still think cm = cm1 + cm2 -- or at least cm = join_colormaps(cm1, cm2) -- is a lot nicer than cm = ListedColormap(np.vstack(cm1.array, cm2.array)).

I apologize for coming across as a disgruntled contributor. I just know quite a few people who find MPL colormaps very cumbersome, and I put time into this because it got positive feedback from some senior MPL devs. I also know there are those out there who think creating/modifying new colormaps is a bad idea, but I find that reasoning arrogant. Hopefully someone will actually work on MEP21 (especially killing LSCs), and perhaps a simple join_colormaps func (and __add__ method) will make sense at that time.

I'll track of your progress on #11905 and see if I have anything to add.

@jklymak
Copy link
Member

jklymak commented Aug 21, 2018

FWIW I share your concern about the opacity of colormaps and how to create them, hence #11905, because info about ListedColormaps is completely missing from the existent docs. Frankly, I didn't know the class existed before Monday. Given that class, however, we can go back and forth relatively painlessly between np arrays and colormaps, it seemed worth trying to improve the documentation first. If the consensus is still that its still too clunky to manipulate colormaps, then we can revisit the idea of allowing the more holistic solution proposed here.

WRT being able to create your own colormaps, I hope I didn't make it sound like people shouldn't be allowed to do that, or that anyone on the dev team thinks that.

In general, the devs are concerned about the balance between "what we can do" and "what we should do". Some features got out of control in the past, and are now maintenance drags on the library. I think the current philosophy is to try to keep things as lightweight as possible and to have fewer black boxes.

@jklymak
Copy link
Member

jklymak commented Sep 28, 2018

Closing in lieu of documentation of how to do the same thing w/ ListedColormaps. @lkilcher again apologies for the long review process that eventually didn't get merged. We really want people to contribute, so we try to avoid this, but sometimes ideas fall between the cracks.

@jklymak jklymak closed this Sep 28, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants