Why does contourf with the extend argument tweak the data limits?

I just spent some time debugging an odd problem. Take this code:

x,y = np.arange(-10,10), np.arange(-10,10)
x,y = np.meshgrid(x,y)
z = x**2+y**2
cs = pyplot.contourf(x,y,z,levels=np.arange(50, 220, 20), cmap=pyplot.cm.jet, extend='both')
cs.cmap.set_under('k')
cb = pyplot.colorbar(cs)

  I was expecting the set_under call to mean that contours outside the passed level range would be painted in that color. This seems to be what the documentation says will happen:

"Unless this [extend argument] is ‘neither’, contour levels are automatically added to one or both ends of the range so that all data are included. These added ranges are then mapped to the special colormap values which default to the ends of the colormap range, but can be set via matplotlib.colors.Colormap.set_under() and matplotlib.colors.Colormap.set_over() methods."

. . .but it doesn't work. Instead, the levels outside my specified range show up colored the same as the lowest selected level (i.e., 50).

  I poked around in the code and found that the culprit is this section in matplotlib.contour.ContourSet._process_levels():

         if self.extend in ('both', 'min'):
             self.vmin = 2 * self.levels[0] - self.levels[1]
         if self.extend in ('both', 'max'):
             self.vmax = 2 * self.levels[-1] - self.levels[-2]

Here, if the "extend" argument is given, the code sets the data limits to some odd extension of the actual data limits. I can fix it and get the correct output by resetting the data limits after plotting my contours:

cs = pyplot.contourf(x,y,z,levels=np.arange(50, 220, 20), cmap=pyplot.cm.jet, extend='both')
cs.cmap.set_under('k')
cs.set_clim(50, 210)
cb = pyplot.colorbar(cs)

  But why do I have to do this? The whole reason I passed in my specified levels was because I wanted THOSE to be the data limits. Why is matplotlib expanding the data limits, and thus preventing me from specifying the "out of range" color using the normal set_under and set_over methods?

···

--
Brendan Barnwell
"Do not follow where the path may lead. Go, instead, where there is no path, and leave a trail."
    --author unknown

You are right, that behavior is very inconsistent with the documentation. Looking over the QuadContourSet._process_levels() method, I doubt the problem lies there. While testing, I noticed that whatever color I set for set_under() was always being ignored in the colorbar. I suspect the problem lies there.

Ben Root

···

On Sun, Jul 8, 2012 at 9:04 PM, Brendan Barnwell <brenbarn@…1219…> wrote:

I just spent some time debugging an odd problem. Take this code:

x,y = np.arange(-10,10), np.arange(-10,10)

x,y = np.meshgrid(x,y)

z = x2+y2

cs = pyplot.contourf(x,y,z,levels=np.arange(50, 220, 20),

cmap=pyplot.cm.jet, extend=‘both’)

cs.cmap.set_under(‘k’)

cb = pyplot.colorbar(cs)

    I was expecting the set_under call to mean that contours outside the

passed level range would be painted in that color. This seems to be

what the documentation says will happen:

"Unless this [extend argument] is ‘neither’, contour levels are

automatically added to one or both ends of the range so that all data

are included. These added ranges are then mapped to the special

colormap values which default to the ends of the colormap range, but

can be set via matplotlib.colors.Colormap.set_under() and

matplotlib.colors.Colormap.set_over() methods."

. . .but it doesn’t work. Instead, the levels outside my specified

range show up colored the same as the lowest selected level (i.e., 50).

    I poked around in the code and found that the culprit is this section

in matplotlib.contour.ContourSet._process_levels():

     if self.extend in ('both', 'min'):

         self.vmin = 2 * self.levels[0] - self.levels[1]

     if self.extend in ('both', 'max'):

         self.vmax = 2 * self.levels[-1] - self.levels[-2]

Here, if the “extend” argument is given, the code sets the data limits

to some odd extension of the actual data limits. I can fix it and get

the correct output by resetting the data limits after plotting my

contours:

cs = pyplot.contourf(x,y,z,levels=np.arange(50, 220, 20),

cmap=pyplot.cm.jet, extend=‘both’)

cs.cmap.set_under(‘k’)

cs.set_clim(50, 210)

cb = pyplot.colorbar(cs)

    But why do I have to do this?  The whole reason I passed in my

specified levels was because I wanted THOSE to be the data limits.

Why is matplotlib expanding the data limits, and thus preventing me

from specifying the “out of range” color using the normal set_under

and set_over methods?

You mean in the colorbar and/or set_under? I don't think so, because the problem occurs whether or not you use a colorbar, and, at least for me, set_under works as long as the data limits are set correctly. If in my code above, I change set_under("k") to set_under("yellow") or whatever else I like, that color is correctly used both in the contourf plot and on the colorbar (on the lower arrow). The code that I pointed to in _process_levels is setting vmin and vmax. It's possible this isn't the root of the problem, but the code certainly looks strange and unmotivated, and it is what's causing incorrect clim to be set.

···

On 2012-07-09 06:13, Benjamin Root wrote:

On Sun, Jul 8, 2012 at 9:04 PM, Brendan Barnwell<brenbarn@...1219...>wrote:

cs = pyplot.contourf(x,y,z,levels=np.arange(50, 220, 20),
cmap=pyplot.cm.jet, extend='both')
cs.cmap.set_under('k')
cs.set_clim(50, 210)
cb = pyplot.colorbar(cs)

         But why do I have to do this? The whole reason I passed in my
specified levels was because I wanted THOSE to be the data limits.
Why is matplotlib expanding the data limits, and thus preventing me
from specifying the "out of range" color using the normal set_under
and set_over methods?

You are right, that behavior is very inconsistent with the documentation.
Looking over the QuadContourSet._process_levels() method, I doubt the
problem lies there. While testing, I noticed that whatever color I set for
set_under() was always being ignored in the colorbar. I suspect the
problem lies there.

--
Brendan Barnwell
"Do not follow where the path may lead. Go, instead, where there is no path, and leave a trail."
    --author unknown

I assure you it was not unmotivated; but I agree that there is a problem here that needs to be solved cleanly. I will look into it.

Eric

···

On 2012/07/09 7:33 AM, Brendan Barnwell wrote:

On 2012-07-09 06:13, Benjamin Root wrote:

On Sun, Jul 8, 2012 at 9:04 PM, Brendan Barnwell<brenbarn@...1219...>wrote:

  cs = pyplot.contourf(x,y,z,levels=np.arange(50, 220, 20),
  cmap=pyplot.cm.jet, extend='both')
  cs.cmap.set_under('k')
  cs.set_clim(50, 210)
  cb = pyplot.colorbar(cs)

          But why do I have to do this? The whole reason I passed in my
  specified levels was because I wanted THOSE to be the data limits.
  Why is matplotlib expanding the data limits, and thus preventing me
  from specifying the "out of range" color using the normal set_under
  and set_over methods?

You are right, that behavior is very inconsistent with the documentation.
Looking over the QuadContourSet._process_levels() method, I doubt the
problem lies there. While testing, I noticed that whatever color I set for
set_under() was always being ignored in the colorbar. I suspect the
problem lies there.

  You mean in the colorbar and/or set_under? I don't think so, because
the problem occurs whether or not you use a colorbar, and, at least
for me, set_under works as long as the data limits are set correctly.
   If in my code above, I change set_under("k") to set_under("yellow")
or whatever else I like, that color is correctly used both in the
contourf plot and on the colorbar (on the lower arrow). The code that
I pointed to in _process_levels is setting vmin and vmax. It's
possible this isn't the root of the problem, but the code certainly
looks strange and unmotivated, and it is what's causing incorrect clim
to be set.

Please check out https://github.com/matplotlib/matplotlib/pull/1022.

Eric

···

On 2012/07/09 7:33 AM, Brendan Barnwell wrote:

On 2012-07-09 06:13, Benjamin Root wrote:

On Sun, Jul 8, 2012 at 9:04 PM, Brendan Barnwell<brenbarn@...1219...>wrote:

  cs = pyplot.contourf(x,y,z,levels=np.arange(50, 220, 20),
  cmap=pyplot.cm.jet, extend='both')
  cs.cmap.set_under('k')
  cs.set_clim(50, 210)
  cb = pyplot.colorbar(cs)

          But why do I have to do this? The whole reason I passed in my
  specified levels was because I wanted THOSE to be the data limits.
  Why is matplotlib expanding the data limits, and thus preventing me
  from specifying the "out of range" color using the normal set_under
  and set_over methods?

You are right, that behavior is very inconsistent with the documentation.
Looking over the QuadContourSet._process_levels() method, I doubt the
problem lies there. While testing, I noticed that whatever color I set for
set_under() was always being ignored in the colorbar. I suspect the
problem lies there.

  You mean in the colorbar and/or set_under? I don't think so, because
the problem occurs whether or not you use a colorbar, and, at least
for me, set_under works as long as the data limits are set correctly.
   If in my code above, I change set_under("k") to set_under("yellow")
or whatever else I like, that color is correctly used both in the
contourf plot and on the colorbar (on the lower arrow). The code that
I pointed to in _process_levels is setting vmin and vmax. It's
possible this isn't the root of the problem, but the code certainly
looks strange and unmotivated, and it is what's causing incorrect clim
to be set.