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

Automatically apply diverging colormaps and add robust=True option #78

Closed
mickaellalande opened this issue Dec 3, 2019 · 10 comments
Closed
Labels
Milestone

Comments

@mickaellalande
Copy link
Contributor

Is there any way to have an automatic scaling of the colorbar when 0 is present, like the default behavior of Cartopy that center the colorbar on 0 and automatically use a diverging colormap?

And in addition, is that possible to use the option robust=True of Cartopy? When I try to use it, it tells me that it is not recognized.

@lukelbd
Copy link
Collaborator

lukelbd commented Dec 3, 2019

(1) Yes there is. There are two options:

ax.[contourf|pcolor|...](data, symmetric=True)  # auto-select levels symmetric about zero
ax.[contourf|pcolor|...](data, norm='midpoint')  # warp the colorbar so that zero falls on the central level, even if asymmetric

(2) Could you post an example? All cartopy.mpl.GeoAxes.[contourf|pcolor|...] keyword argument should be valid, not sure why this would happen.

@lukelbd lukelbd added the bug label Dec 3, 2019
@bradyrx
Copy link
Collaborator

bradyrx commented Dec 3, 2019

Both of these are features of xarray's .plot() API. So you can access them just by using the xarray API, which wraps matplotlib. So all of proplot's features also get incorporated. Well , I guess not the plotting wrappers but at least formatters and so on.

@lukelbd
Copy link
Collaborator

lukelbd commented Dec 3, 2019

@bradyrx So xarray automatically selects a diverging map and applies the equivalent of symmetric=True? If so definitely want to include that; I'm trying to reproduce most basic xarray/pandas features in proplot.

@lukelbd
Copy link
Collaborator

lukelbd commented Dec 3, 2019

Oh robust=True is an xarray feature, not cartopy.

@mickaellalande do you mean that robust=True fails when you pass it to xr.Dataset.plot? Because that definitely shouldn't happen.

Currently ax.[pcolor|pcolormesh|etc] does not accept a robust keyword arg but I will definitely add it for the next version! Just a couple extra lines of code.

@lukelbd lukelbd added feature and removed bug labels Dec 3, 2019
@bradyrx
Copy link
Collaborator

bradyrx commented Dec 3, 2019

@bradyrx So xarray automatically selects a diverging map and applies the equivalent of symmetric=True?

I didn't know about the symmetric keyword, but I imagine that is what is happening. If you pass in values that range from neg to pos, it centers on zero and applies a diverging colormap.

Oh robust=True is an xarray feature, not cartopy. @mickaellalande do you mean that robust=True fails when you pass it to xr.Dataset.plot? Because that definitely shouldn't happen. Currently ax.[pcolor|pcolormesh|etc] does not accept a robust keyword arg but I will definitely add it for the next version! Just a couple extra lines of code.

That's what I suspect. I think @mickaellalande is passing robust to cartopy, although it is a keyword devised by xarray. It just ensures extremes are plotted.

@lukelbd I would just scroll through that xarray plot section of their docs some time to see what you're missing if your goal is to replicate their functionality. They have some very useful things, but their plotting API is of course very limited. It's written for quick looks at the data, not publication-ready viz. So it would be nice to have those features directly from the proplot API.

@mickaellalande
Copy link
Contributor Author

mickaellalande commented Dec 4, 2019

Sorry not to have been precise enough and without example, but I guess you understood what I meant. So I am going to illustrate what I was meaning with xarray/cartopy and then with ProPlot (lot of figures coming up!):

Data example: season_clim_diff.zip

  • Code to reproduce for Xarray/Cartopy behavior:
import xarray as xr
import numpy as np
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
from cartopy.mpl.gridliner import LONGITUDE_FORMATTER, LATITUDE_FORMATTER

season_clim_diff = xr.open_dataarray('season_clim_diff.nc')
season = "SON"

plt.figure(figsize=(10,4))
ax = plt.axes(projection=ccrs.PlateCarree(180))
season_clim_diff.sel(season=season).plot.contourf(ax=ax, transform=ccrs.PlateCarree())
ax.coastlines()
ax.set_global()

gl = ax.gridlines(crs=ccrs.PlateCarree(), draw_labels=True, linewidth=0.5, color='gray', alpha=0.5, linestyle='--')
gl.xlabels_top = False
gl.ylabels_right = False
gl.xformatter = LONGITUDE_FORMATTER
gl.yformatter = LATITUDE_FORMATTER

cartopy_diverging_colormap

As you can see, by default when we write data.plot.contourf(), Xarray/Cartopy makes a symmetric color bar with a diverging colormap.

  • Then I can use the option robust=True in my plot option (I replace in the previous code):
season_clim_diff.sel(season=season).plot.contourf(ax=ax, transform=ccrs.PlateCarree(), robust=True)

cartopy_diverging_robust_colormap

This will use the 2nd and 98th percentiles of the data to compute the color limits. (http://xarray.pydata.org/en/stable/plotting.html#robust)

It is usually convenient. Except when it extends only one side of the color bar (like in this example) and makes the color bar not symmetric anymore (shifted of one bin). I don't know if it is possible to fix this... I usually put extend='both' to escape the problem, but it is not the best. Also the problem of overlapping 180° but I tried to address it in an issue in Cartopy (SciTools/cartopy#1401).

  • Then with ProPlot:
import proplot as plot
season = "SON"
f, axs = plot.subplots(proj='cyl', proj_kw={'lon_0':180}, width=6)

m = axs[0].contourf(season_clim_diff.sel(season=season))

f.colorbar(m, label="Snow Area Fraction [%]")
axs.format(geogridlinewidth=0.5, geogridcolor='gray8', geogridalpha=0.5, labels=True, 
           coast=True, suptitle=season+" snow cover bias", ocean=True, oceancolor='gray4')

proplot

That was what I was meaning, it would be nice to have automatically a diverging colormap centered on 0 (symmetric or not?). I didn't know about these options (symmetric and norm) and it is actually really convenient, thank you @lukelbd! Here are examples with these options by changing in the previous code (and adding a diverging cmap):

m = axs[0].contourf(season_clim_diff.sel(season=season), cmap='ColdHot', symmetric=True)

proplot_symmetric_diverging_cmap

or

m = axs[0].contourf(season_clim_diff.sel(season=season), cmap='ColdHot', norm='midpoint')

proplot_norm_diverging_cmap

And if I try to reproduce the robust colormap with xarray/cartopy, I put some levels by hand:

m = axs[0].contourf(season_clim_diff[0], cmap='ColdHot', levels=np.arange(-45,50,5))

proplot_levels

And with the extend='max' it also shift the color map of one bin... even with the norm='midpoint':

m = axs[0].contourf(season_clim_diff[0], cmap='ColdHot', levels=np.arange(-45,50,5), extend='max', norm='midpoint')

proplot_levels_extend_max

A bit long, but I hope all these examples can help to improve proplot! (and/or makes me discover other option that I don't know). Thanks again for the quit reply and involvement!

@lukelbd lukelbd added this to the Version 1 milestone Dec 4, 2019
@lukelbd lukelbd changed the title Diverging colormaps centered on 0 and robust option of cartopy Diverging colormaps centered on 0 and duplicate gridliners on GeoAxes Dec 4, 2019
@lukelbd
Copy link
Collaborator

lukelbd commented Dec 4, 2019

@mickaellalande Thanks for the examples! Yes extend='max' or extend='min' will always result in asymmetric colors unless you also use norm='midpoint'; I think it's reasonable to keep this behavior the same since if you want "symmetric" colors it makes sense to require a "symmetric" extend setting.

I'll add a feature where if the minimum negative (maximum positive) value is at least 10% or 20% of the magnitude of the maximum positive (minimum negative) value, a diverging colormap is automatically selected. Or could use percentiles for this test. And maybe will allow this threshold to be configurable.

Not sure what the deal is with that gridliner issue, will check it out.

@lukelbd lukelbd added the bug label Dec 4, 2019
@lukelbd
Copy link
Collaborator

lukelbd commented Dec 4, 2019

Btw proplot has a plot.arange(...) function that's great for generating lists of ticks, colorbar levels, etc. Behavior is identical to np.arange except it is endpoint inclusive. So your example would be plot.arange(-45, 45, 5) rather than np.arange(-45, 50, 5) which IMO is more intuitive.

@lukelbd lukelbd changed the title Diverging colormaps centered on 0 and duplicate gridliners on GeoAxes Diverging colormaps centered on 0 Dec 5, 2019
@mickaellalande
Copy link
Contributor Author

Btw proplot has a plot.arange(...) function that's great for generating lists of ticks, colorbar levels, etc. Behavior is identical to np.arange except it is endpoint inclusive. So your example would be plot.arange(-45, 45, 5) rather than np.arange(-45, 50, 5) which IMO is more intuitive.

Thanks, I find this also more convenient!

@lukelbd lukelbd removed the bug label Dec 6, 2019
@lukelbd lukelbd changed the title Diverging colormaps centered on 0 Automatically apply diverging colormaps and add robust=True option Dec 9, 2019
@proplot-dev proplot-dev deleted a comment from mickaellalande Feb 10, 2020
@lukelbd
Copy link
Collaborator

lukelbd commented Aug 18, 2021

These features are finally on master. Below is an example of the robust=True feature. You can also pass a number e.g. robust=90 to select colormap limits based on the middle 90 percentile range, or pass a tuple e.g. robust=(0, 90) to use that particular percentile range. As in xarray, the default percentile range implied by robust=True is 96. The keyword choice is maybe a bit clunky here since robust can now accept these other inputs but I'd rather stay consistent with xarray.

import proplot as pplt
import numpy as np
fig, axs = pplt.subplots(ncols=2)
N = 10
data = np.random.rand(N, N)
data.flat[0] = 100.0
for i, ax in enumerate(axs):
    ax.pcolor(data, robust=bool(i))
    ax.format(title='Robust ' + ('on' if i else 'off'))

test

And here's an example of the automatic diverging colormap selection. A diverging colormap is picked as follows:

  • If discrete=True and levels are being used, then levels must have more than one each positive and negative values.
  • If discrete=False and only vmin and vmax are being used, then vmin and vmax must have the opposite sign.

I've also added a whole system for selecting default "sequential", "diverging", "qualitative", and "cyclic" colormaps with corresponding rc settings and keyword arguments that can be passed to plot commands (e.g., rc['cmap.cyclic'] = cmap or to change the default cyclic colormap and ax.pcolor(..., cyclic=True) to select the default cyclic colormap). Details are in the changelog on master (about to put these in a big release).

import proplot as pplt
import numpy as np
fig, axs = pplt.subplots(ncols=2)
N = 10
data = np.random.rand(N, N) * 10
for i, ax in enumerate(axs):
    ax.pcolor(data - i * 5, colorbar='b')
    ax.format(title='Diverging ' + ('on' if i else 'off'))

div

@lukelbd lukelbd closed this as completed Aug 18, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants