Contourf not showing full range of values

Viewed 36

I have two datasets that when compared result in a basically random distribution of values between -1 and 1. When I plot this using contourf, however, the figure shows almost all values > 0.5. When I plot every 10th point (thin the data), I get a graph that is more reasonable. But it is not clear why the contourf function is doing this.

I replicated this using a random number list of the same size as my data. The result is the same.

import numpy as np
import matplotlib.pyplot as plt
from netCDF4 import Dataset
from matplotlib.cm import get_cmap
import numpy as np

random = np.random.random((360,1600))*2.-1.

f, ax = plt.subplots(1,2,figsize=(15,5))

#heights = ax.contour(to_np(hgt),3,colors='k')
#ax.clabel(heights, fmt='%2.0f', colors='k', fontsize=8)
#cbar = f.colorbar(heights)
#heights.levels=[0,100,3000]
#plt.clabel(heights, heights.levels)

clevs = [-0.5,-0.1,0.1,0.5]
diffplot = ax[0].contourf(random[::10,::10],clevs,extend='both')

cbar = f.colorbar(diffplot,ax=ax[0])

clevs = [-0.5,-0.1,0.1,0.5]
diffplot2 = ax[1].contourf(random[:,:],clevs,extend='both')

cbar = f.colorbar(diffplot2,ax=ax[1])

Result of code

1 Answers

The full range is shown, but the issue is in the large number of points vs. resolution.

That means points drawn 'later' (aka the ones at the end of the color levels range = higher values) have a certain minimum size (you still need to see them) that then overlaps previous (aka smaller values) points, resulting in the image colors looking skewed.


Unfortunately not a solution but probably at least an explanation - bare with me for some plots:

Note: The following plots are arranged with gridspec to be shown in a single figure. That way the 'resolution' is depicted in a comparable way.

1) The effect you recognized depends on the plot size

See the code below, all 3 plots contain the same data ..., the only difference is their size.
Notice how with the increased size the colors distribution looks more and more as expected.

enter image description here

from matplotlib import gridspec
import numpy as np
import matplotlib.pyplot as plt


random = np.random.random((360,1600))*2.-1.
#random = np.random.random((100,100))*2.-1.

clevs = [-0.5,-0.1,0.1,0.5]

fig = plt.figure(figsize=(18,20), facecolor=(1, 1, 1))
gs = gridspec.GridSpec(3, 4, height_ratios=[1,1,4]) 

cmap='viridis'  # Note: 'viridis' is the default cmap


ax1 = fig.add_subplot(gs[0,:1])
ax1.set_title('ax1')
diffplot1 = ax1.contourf(random[:,:],clevs,extend='both', cmap=cmap)
fig.colorbar(diffplot1, ax=ax1)

ax2 = fig.add_subplot(gs[0:2,2:])
ax2.set_title('ax2')
diffplot2 = ax2.contourf(random[:,:],clevs,extend='both', cmap=cmap)
fig.colorbar(diffplot2, ax=ax2)
    
ax3 = fig.add_subplot(gs[2,:])
ax3.set_title('ax3')
diffplot3 = ax3.contourf(random[:,:],clevs,extend='both', cmap=cmap)
fig.colorbar(diffplot3, ax=ax3)


fig.tight_layout() 
# plt.savefig("Contourf_Colorbar.png")
plt.show()

2) The effect you recognized depends on number of points 'cramped' into a plot

Basically the same that you've already noticed in your question with plotting only every 10th value. Notice how the colors distribution looks as expected kinda the same for the 3 plot sizes.

enter image description here

Activate random = np.random.random((100,100))*2.-1. in the code block above to get this plot.

3) Reversed color cmap as another way of showing the effect

Notice how this is like the plot from 1) but just with reversed colors.

enter image description here

from matplotlib import gridspec
import numpy as np
import matplotlib.pyplot as plt


random = np.random.random((360,1600))*2.-1.
clevs = [-0.5,-0.1,0.1,0.5]

fig = plt.figure(figsize=(18,20), facecolor=(1, 1, 1))
gs = gridspec.GridSpec(3, 4, height_ratios=[1,1,4]) 

# reverse cmap
cmap='viridis'  # Note: 'viridis' is the default cmap
cmap=plt.cm.get_cmap(cmap)
cmap = cmap.reversed()
    

ax1 = fig.add_subplot(gs[0,:1])
ax1.set_title('ax1')
diffplot1 = ax1.contourf(random[:,:],clevs,extend='both', cmap=cmap)
fig.colorbar(diffplot1, ax=ax1)
    
ax2 = fig.add_subplot(gs[0:2,2:])
ax2.set_title('ax2')
diffplot2 = ax2.contourf(random[:,:],clevs,extend='both', cmap=cmap)
fig.colorbar(diffplot2, ax=ax2)
    
ax3 = fig.add_subplot(gs[2,:])
ax3.set_title('ax3')
diffplot3 = ax3.contourf(random[:,:],clevs,extend='both', cmap=cmap)
fig.colorbar(diffplot3, ax=ax3)


fig.tight_layout() 
# plt.savefig("Contourf_Colorbar_reverse.png")
plt.show()

Workarounds: The one you already came up with = reduce the numbers of points being plotted. Or increase the plot size as shown.

Basically you won't 'loose' information as the resolution isn't there to show the information anyway.


Finally for reference from matplotlib contourf docu:

algorithm{'mpl2005', 'mpl2014', 'serial', 'threaded'}, optional

Which contouring algorithm to use to calculate the contour lines and polygons. The algorithms are implemented in ContourPy, consult the ContourPy documentation for further information.

The default is taken from rcParams["contour.algorithm"] (default: 'mpl2014').

I've tried some options (wasn't able to get algorithm going, but checked antialiased ... actually more in a try&error method) without an improvement.

But you could look into the referenced ContourPy to maybe find a way to reduce the 'size of the dots' that are drawn, but that's out of my league.

Related