linear regression fit plot over boxplots in shared y-axis - matplotlib

I have a plot in the picture below:
Is it possible to add a colored band to indicate a linear regression between the different x-axis? I want a plot like this, with filling with the same color all the zone between the two green lines:

A quick and dirty solution, to create a visually equal single plot, would be to use range(1,17) for x values and use the matplotlib functions xticks, grid and axvline to fine tune the plot:
# fake some data
xs = range(1, 17)
vals = np.asarray([0.73, 0.74, 0.73, 0.71,
0.75, 0.76, 0.75, 0.73,
0.77, 0.78, 0.77, 0.75,
0.79, 0.80, 0.79, 0.77])
data = np.random.rand(20, len(vals)) * 0.03 + vals
avgs = np.mean(data, axis=0)
# plot linear regr. lines and fill
xs2 = [0,20]
coef = np.polyfit(xs[0::4], avgs[0::4], 1) # values for 0.01
ys2a = np.polyval(coef, xs2)
coef = np.polyfit(xs[3::4], avgs[3::4], 1) # values for 0.5
ys2b = np.polyval(coef, xs2)
plt.fill_between(xs2, ys2a, ys2b, color='OliveDrab', alpha=0.5)
plt.plot(xs2, ys2a, color='OliveDrab', lw=3)
plt.plot(xs2, ys2b, color='OliveDrab', lw=3)
# plot data and manipulate axis and grid
plt.boxplot(data, showfliers=False)
plt.xticks(xs, [0.01, 0.1, 0.2, 0.5] * 4)
plt.xlim(0.5, 16.5)
plt.grid(False)
for i in range(3):
plt.axvline(i * 4 + 4.5, c='white')
plt.xlabel('$\sigma^{2}$')
plt.ylabel('$F_{w}(t)$')
plt.show()

Related

Fill between two horizontal lines thresholds in matplotlib

Dears, I need to make a fill between two thresholds in my chart I have tried with my code but it does not display, could you please tell me what I am doing wrong? I would like my figure like contour red like figure 2
fig = plt.figure(figsize=(15, 6))
ax = fig.add_subplot(111)
ax = lacebita['Prob'].plot(figsize=(15, 7), )
xtime = np.linspace(1990,2021,384)
ax.plot(xtime, lacebita['Prob'], 'black', alpha=1.00, linewidth=2, label = 'Deciles')
ax.fill_between(xtime, 0., lacebita['Prob'], lacebita['Prob']< 30., color='red', alpha=.75)
ax.axhline(50, linestyle='--', color='black',label='Percentile 50')
ax.axhline(33, linestyle='--', color='orange', label='Percentile 33')
ax.set_xlim(1990, 2021)
ax.set_ylim(0, 100, 10)
plt.grid(True)
plt.legend(loc = "upper left")
#ax.autoscale_view()
ax.set_title('Deciles para 12-Meses La Cebita(1990-2020)', fontsize=16)
ax.set_xlim(lacebita.index.min(), lacebita.index.max())
plt.savefig('deciles_12_lacebita.jpg')
There are a couple of ways to go about it. One approach is to fill the space in between the two horizontal threshold lines:
# Make a fake function
t = 20
fs = 10
samples = np.linspace(0, t, int(fs*t), endpoint=False)
wave_y = np.sin(samples)
time_x = np.arange(0, len(wave_y))
# Set upper and lower thresholds where horizontal lines will go and fill in between
upper_th = 0.5
lower_th = -0.5
# Plot function
fig, ax = plt.subplots()
ax.plot(time_x, wave_y)
ax.grid()
ax.set_ylim([-1.25, 1.25])
ax.set_ylabel('y label')
ax.set_xlim([0, 125])
ax.set_xlabel('x label')
# Fill in area under the curve and the horizontal lines
ax.fill_between(x=time_x, y1=upper_th, y2=lower_th, color='red', interpolate=True, alpha=.75)
# Horizontal lines
ax.axhline(upper_th, linestyle='--', color='black', label="upper_th: 0.5")
ax.axhline(lower_th, linestyle='--', color='orange', label='lower_th: - 0.5')
ax.legend()
plt.show()
Or if you change y1 or y2, for example to y1=0, you can play around with where exactly the fill is.
Another method is to fill in between the curve and the horizontal dashed lines. To do that you could modify the original data so that the values that go above the upper threshold and below the lower threshold become the threshold values. In other words, we want to make a new y curve that includes the threshold points by eliminating the points that go above/below the threshold so that matplotlib understands that the horizontal lines are part of the y curve.
# Copy original data, we are now going to modify
new_wave_y = np.copy(wave_y)
# Change values outside thresholds to threshold value for fill in
new_wave_y[new_wave_y < lower_th] = lower_th
new_wave_y[new_wave_y > upper_th] = upper_th
This way we can use where in fill between to point out where exactly under the curve, including under/above the horizontal lines, matplotlib needs to fill in the area. The full script:
# Make a fake function
t = 20
fs = 10
samples = np.linspace(0, t, int(fs*t), endpoint=False)
wave_y = np.sin(samples)
time_x = np.arange(0, len(wave_y))
# Set upper and lower thresholds where horizontal lines will go and fill in between
upper_th = 0.5
lower_th = -0.5
# Copy original data, we are now going to modify
new_wave_y = np.copy(wave_y)
# Change values outside thresholds to threshold value for fill in
new_wave_y[new_wave_y < lower_th] = lower_th
new_wave_y[new_wave_y > upper_th] = upper_th
# Plot function
fig, ax = plt.subplots()
ax.plot(time_x, wave_y)
ax.grid()
ax.set_ylim([-1.25, 1.25])
ax.set_ylabel('y label')
ax.set_xlim([0, 125])
ax.set_xlabel('x label')
# Fill in area under the curve and the horizontal lines
ax.fill_between(x=time_x, y1=new_wave_y, where=(lower_th < new_wave_y), color='red', interpolate=True, alpha=.75)
ax.fill_between(x=time_x, y1=new_wave_y, where=(new_wave_y < upper_th), color='red', interpolate=True, alpha=.75)
# Horizontal lines
ax.axhline(upper_th, linestyle='--', color='black', label="upper_th: 0.5")
ax.axhline(lower_th, linestyle='--', color='orange', label='lower_th: - 0.5')
ax.legend()
plt.show()
You can get some more information in the Matplotlib fill between demo and the fill between docs.
Edit:
If you want to fill in below or above the threshold line, for example fill in below the lower threshold, you can modify the y curve so that the values above the threshold become the threshold value (same as before but reverse) and change the values in fill_between . The full script:
# Make a fake function
t = 20
fs = 10
samples = np.linspace(0, t, int(fs*t), endpoint=False)
wave_y = np.sin(samples)
time_x = np.arange(0, len(wave_y))
# Set upper and lower thresholds where horizontal lines will go and fill in between
upper_th = 0.5
lower_th = -0.5
# Copy original data, we are now going to modify
new_wave_y = np.copy(wave_y)
# Change values outside thresholds to threshold value for fill in
new_wave_y[new_wave_y > lower_th] = lower_th
# Plot function
fig, ax = plt.subplots()
ax.plot(time_x, wave_y)
ax.grid()
ax.set_ylim([-1.25, 1.25])
ax.set_ylabel('y label')
ax.set_xlim([0, 125])
ax.set_xlabel('x label')
# Fill in area under the curve and the horizontal lines
ax.fill_between(x=time_x, y1=new_wave_y, y2=lower_th, where=(new_wave_y < lower_th), color='red', interpolate=True, alpha=.75)
# Horizontal lines
ax.axhline(upper_th, linestyle='--', color='black', label="upper_th: 0.5")
ax.axhline(lower_th, linestyle='--', color='orange', label='lower_th: - 0.5')
ax.legend()
plt.show()

How to plot same colors for same values in a map?

I'm creating a colorbar with the function make_colormap. Source: Create own colormap using matplotlib and plot color scale.
Also i'm plotting many maps with for month, data in normals.groupby('MONTH'):
I want to create a color bar with the same values for the same colors (to be able to compare values in maps) but in the:
rvb = make_colormap(
[c('brown'), c('orange'), 0.10, c('orange'), c('yellow'), 0.20, c('green'), c('cyan'), 0.66, c('blue'), c('purple') ])
I can only put percentages. Do you know how can i modify this to put exact values instead of percentages?
import matplotlib.colors as mcolors
def make_colormap(seq):
"""Return a LinearSegmentedColormap
seq: a sequence of floats and RGB-tuples. The floats should be increasing
and in the interval (0,1).
"""
seq = [(None,) * 3, 0.0] + list(seq) + [1.0, (None,) * 3]
cdict = {'red': [], 'green': [], 'blue': []}
for i, item in enumerate(seq):
if isinstance(item, float):
r1, g1, b1 = seq[i - 1]
r2, g2, b2 = seq[i + 1]
cdict['red'].append([item, r1, r2])
cdict['green'].append([item, g1, g2])
cdict['blue'].append([item, b1, b2])
return mcolors.LinearSegmentedColormap('CustomMap', cdict)
c = mcolors.ColorConverter().to_rgb
rvb = make_colormap(
[c('brown'), c('orange'), 0.10, c('orange'), c('yellow'), 0.20, c('green'), c('cyan'), 0.66, c('blue'), c('purple') ])
for month, data in normals.groupby('MONTH'):
lons, lats= np.array(data['LONGITUDE']), np.array(data['LATITUDE'])
ppvalues=np.array(data['PP']).astype(int)
month = data['MONTH'].iloc[0]
fig = plt.figure('map', figsize=(7,7), dpi=200)
ax = fig.add_axes([0.1, 0.12, 0.80, 0.75], projection=ccrs.PlateCarree())
plt.xlabel('LONGITUDE')
plt.ylabel('LATITUDE')
ax.outline_patch.set_linewidth(0.3)
l = NaturalEarthFeature(category='cultural', name='admin_0_countries', scale='50m', facecolor='none')
ax.add_feature(l, edgecolor='black', linewidth=0.25)
img = ax.scatter(lons, lats, s=7, c=ppvalores, cmap=rvb,
marker='o', transform=ccrs.PlateCarree())
#ticks=[0,1,2,3,4,5,6,7,8,9,10]
cb = plt.colorbar(img, extend='both',
spacing='proportional', orientation='horizontal',
cax=fig.add_axes([0.12, 0.12, 0.76, 0.02]))
plt.show()
fig.savefig("path/".png")
I'm relatively new in python so would you mind to help me?
Thanks in advance.
You could apply a norm. Using the same norm for all plots would make the colors consistent. It is unclear what the range of your data['PP'] column is. Here is an example of the changes if you would like 100, 200 and 660 for the three values in the list given to make_colormap:
vmin = data['PP'].min() # the overall minimum
vmax = data['PP'].max() # the overall maximum
norm = plt.Normalize(vmin, vmax) # function that maps the range of data['PP'] to the range [0,1]
rvb = make_colormap(
[c('brown'), c('orange'), norm(100), c('orange'), c('yellow'), norm(200), c('green'), c('cyan'), norm(660), c('blue'), c('purple')])
for month, data in normals.groupby('MONTH'):
...
img = ax.scatter(..., cmap=rvb, norm=norm)
...

Matplotlib subplots how to align colorbars with other legends, or how to justify subplots to left

How can I add a colorbar scale to the 2nd & 3rd subplots, such that it is inline with my legends in the 1st and 4th subplots? Or, another way to say the question: how can I add a colorbar scale without changing the alignment/justification of the 2nd & 3rd subplots?
There are good examples available on setting colorbar locations (e.g., here on stackoverflow and in the matplotlib docs), but I still haven't been able to solve this.
Below is a reproducible example. The real data are more complicated, and this is part of a loop to produce many figures, so the "extra" stuff about setting axis limits and subplot aspect ratios is needed and will change with different datasets.
Using Python 3.8.
Reproducible example without colorbar
## Specify axes limits, tick intervals, and aspect ratio
xl, yl, xytick, ar = [-40000,120000], [-30000,10000], 20000, 0.8
## Global plot layout stuff
fig = plt.figure(figsize=(10, 7.5), constrained_layout=True)
gs = fig.add_gridspec(4, 1)
ax1 = fig.add_subplot(gs[0, 0])
ax2 = fig.add_subplot(gs[1, 0], sharex = ax1, sharey = ax1)
ax3 = fig.add_subplot(gs[2, 0], sharex = ax1)
ax4 = fig.add_subplot(gs[3, 0], sharex = ax1, sharey = ax3)
fig.execute_constrained_layout()
fig.suptitle('Suptitle')
## First Plot
ax1.plot([-30000, 500], [-2000, -21000], c='red', label='A')
ax1.plot([80000, 110000], [-9000, 800], c='blue', label='B')
ax1.set_title('ax1', style='italic');
ax1.set_xlabel('x');
ax1.set_ylabel('beta');
ax1.set_xlim(xl)
ax1.set_ylim(yl)
ax1.xaxis.set_major_locator(ticker.MultipleLocator(xytick))
ax1.yaxis.set_major_locator(ticker.MultipleLocator(xytick))
ax1.legend(handles=leg, bbox_to_anchor=(1.05, 1), loc='upper left')
ax1.set_aspect(aspect=ar)
## Dummy data for plots 2/3/4
x = [-15000, -2000, 0, 5000, 6000, 11000, 18000, 21000, 25000, 36000, 62000]
beta = [1000, 200, -800, 100, 1000, -2000, -5000, -5000, -15000, -21000, -1500]
y = [0.01, 0.2, 1.3, 0.35, 0.88, 2.2, 2.5, 1.25, 3.4, 4.1, 2.1]
## Second Plot
vals = ax2.scatter(x, beta, c=y, norm=mcolors.LogNorm(), cmap='rainbow')
ax2.set_title('ax2', style='italic');
ax2.set_xlabel('x');
ax2.set_ylabel('beta');
ax2.set_aspect(aspect=ar)
## Attempt to add colorbar
#cbar = fig.colorbar(vals, ax=ax2, format = '%1.2g', location='right', aspect=25)
#cbar.ax.set_ylabel('y')
#cbar.ax.yaxis.set_label_position('left')
#cbar_range = [min(y), max(y)]
#ticklabels = cbar.ax.get_ymajorticklabels()
#cbarticks = list(cbar.get_ticks())
#cbar.set_ticks(cbar_range + cbarticks)
## Third Plot
ax3.scatter(x, y, c=y, norm=mcolors.LogNorm(), cmap='rainbow')
ax3.set_title('ax3', style='italic');
ax3.set_xlabel('x');
ax3.set_ylabel('y');
ax3.yaxis.set_major_formatter(FormatStrFormatter('%1.2g'))
## Fourth Plot
ax4.scatter(x, y, c='black', label='Dots')
ax4.set_title('ax4', style='italic');
ax4.set_xlabel('x');
ax4.set_ylabel('y');
ax4.legend(bbox_to_anchor=(1.05, 1), loc='upper left')
## Clean-up, set aspect ratios
figW, figH = ax1.get_figure().get_size_inches()
_, _, w, h = ax1.get_position().bounds
disp_ratio = (figH * h) / (figW * w)
data_ratio = sub(*ax3.get_ylim()) / sub(*ax3.get_xlim())
ax3.set_aspect(aspect=disp_ratio / data_ratio )
ax4.set_aspect(aspect=disp_ratio / data_ratio)
## Clean-up, turn axis ticks back on after messing with cbar
#ax1.tick_params(axis='both', which='both', labelbottom='on')
#ax2.tick_params(axis='both', which='both', labelbottom='on')
#ax3.tick_params(axis='both', which='both', labelbottom='on')
Result when trying colorbar, note misalignment of second plot
Suggest you simplify your code and make sure it all works; for instance I have no idea what sub does.
A partial solution to your problem could be panchor=False, which is a bit of an obscure kwarg, but...
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
## Specify axes limits, tick intervals, and aspect ratio
ar = 1.2
## Global plot layout stuff
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 4), constrained_layout=True, sharex=True, sharey=True)
## First Plot
ax1.plot([-20_000, 20_000], [-20_000, 20_000] )
ax1.set_aspect(aspect=ar)
## Dummy data for plots 2/3/4
x = [-15000, -2000, 0, 5000, 6000, 11000, 18000, 21000, 25000, 36000, 62000]
beta = [1000, 200, -800, 100, 1000, -2000, -5000, -5000, -15000, -21000, -1500]
y = [0.01, 0.2, 1.3, 0.35, 0.88, 2.2, 2.5, 1.25, 3.4, 4.1, 2.1]
## Second Plot
vals = ax2.scatter(x, beta, c=y, norm=mcolors.LogNorm(), cmap='rainbow')
ax2.set_aspect(aspect=ar)
cbar = fig.colorbar(vals, ax=ax2, format = '%1.2g', location='right',
aspect=25, panchor=False)
plt.show()
Depending on the size of the figure, this could comically place the colorbar far to the right. The problem here is the aspect ratio of your plots, which makes the actual axes more narrow than the figure. But the colorbar doesn't really know about that, and places itself on the outside of the space allocated for the axes.
If this is displeasing, then you can also specify an inset axes for the colorbar.
cbax = ax2.inset_axes([1.05, 0.2, 0.05, 0.6], transform=ax2.transAxes)
cbar = fig.colorbar(vals, cax=cbax, format = '%1.2g', orientation='vertical')
Using inset_axes() solves this, as suggested in the other answer, but the parameters relative to the transform were not explained in the example, but I was able to figure it out with some research.
The parameters in inset_axes are [x-corner, y-corner, width, height] and the transform is like a local reference. So, using [1,0,0.5,0.75] means: x = 100% or end of parent ax; y = 0% or bottom of parent ax; width = 50% of parent ax; and height = 75% of parent ax.
Here I wanted the colorbar to be the same height as the parent ax (ax2 and ax3), very thin, and offset a little bit to be more in line with the other legends. Using cbax = ax2.inset_axes([1.1, 0, 0.03, 1], transform=ax2.transAxes) achieves this.
This code works for any aspect ratio ar.
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import matplotlib.colors as mcolors
from operator import sub
%matplotlib inline
plt.style.use('seaborn-whitegrid')
## Specify axes limits, tick intervals, and aspect ratio
xl, yl, ar = [-40000,120000], [-30000,10000], .5
## Global plot layout stuff
fig = plt.figure(figsize=(10, 7.5), constrained_layout=True)
gs = fig.add_gridspec(4, 1)
ax1 = fig.add_subplot(gs[0, 0])
ax2 = fig.add_subplot(gs[1, 0], sharex = ax1, sharey = ax1)
ax3 = fig.add_subplot(gs[2, 0], sharex = ax1)
ax4 = fig.add_subplot(gs[3, 0], sharex = ax1, sharey = ax3)
fig.execute_constrained_layout()
fig.suptitle('Suptitle')
## First Plot
ax1.plot([-30000, 500], [-2000, -21000], c='red', label='A')
ax1.plot([80000, 110000], [-9000, 800], c='blue', label='B')
ax1.set_title('ax1', style='italic');
ax1.set_xlim(xl)
ax1.set_ylim(yl)
ax1.legend(bbox_to_anchor=(1.05, 1), loc='upper left')
ax1.set_aspect(aspect=ar)
## Dummy data for plots 2/3/4
x = [-15000, -2000, 0, 5000, 6000, 11000, 18000, 21000, 25000, 36000, 62000]
beta = [1000, 200, -800, 100, 1000, -2000, -5000, -5000, -15000, -21000, -1500]
y = [0.01, 0.2, 1.3, 0.35, 0.88, 2.2, 2.5, 1.25, 3.4, 4.1, 2.1]
## Second Plot
vals = ax2.scatter(x, beta, c=y, norm=mcolors.LogNorm(), cmap='rainbow')
ax2.set_title('ax2', style='italic');
ax2.set_aspect(aspect=ar)
cbax = ax2.inset_axes([1.1, 0, 0.03, 1], transform=ax2.transAxes)
cbar2 = fig.colorbar(vals, cax=cbax, format = '%1.2g', orientation='vertical')
## Third Plot
ax3.scatter(x, y, c=y, norm=mcolors.LogNorm(), cmap='rainbow')
ax3.set_title('ax3', style='italic');
cbax = ax3.inset_axes([1.1, 0, 0.03, 1], transform=ax3.transAxes)
cbar3 = fig.colorbar(vals, cax=cbax, format = '%1.2g', orientation='vertical')
## Fourth Plot
ax4.scatter(x, y, c='black', label='Dots')
ax4.set_title('ax4', style='italic');
ax4.legend(bbox_to_anchor=(1.05, 1), loc='upper left')
## Clean-up, set aspect ratios
figW, figH = ax1.get_figure().get_size_inches()
_, _, w, h = ax1.get_position().bounds
disp_ratio = (figH * h) / (figW * w)
data_ratio = sub(*ax3.get_ylim()) / sub(*ax3.get_xlim())
ax3.set_aspect(aspect=disp_ratio / data_ratio )
ax4.set_aspect(aspect=disp_ratio / data_ratio)
## Colorbars
cbar2.ax.set_ylabel('y')
cbar2.ax.yaxis.set_label_position('left')
cbar3.ax.set_ylabel('y')
cbar3.ax.yaxis.set_label_position('left')
Result with aspect ratio = 0.5 for top 2 plots
Result with aspect ratio = 2 for top 2 plots

How to avoid overlapping bars in plt.bar when x-values aren't spaced evenly?

I have
x = array([0., 0.08, 0.12, 0.18, 0.27, 0.42, 0.65])
y = array([0., 0.03758546, 0.06577713, 0.48786205, 0.28553257, 0.09909356, 0.02414922])
I then write
plt.ticklabel_format(useOffset=False)
plt.figure(figsize=(20,10))
plt.xlabel('D/Dmax')
plt.bar(x, y), align = 'edge', tick_label = x, color = 'red', edgecolor = "black")
And I get the following chart. Why is it like this, and how can I make the bars not overlap and distinct like every other bar chart?
As your bars don't have a constant width, you can calculate these widths as the difference between the x-values: np.diff(x). Note that there is one less difference than there are elements in x. To get a width for the last bar (which in theory could be infinite), you can either repeat the next-to-last width, or add an extra x-value to set the rightmost boundary.
from matplotlib import pyplot as plt
import numpy as np
x = np.array([0., 0.08, 0.12, 0.18, 0.27, 0.42, 0.65])
y = np.array([0., 0.03758546, 0.06577713, 0.48786205, 0.28553257, 0.09909356, 0.02414922])
widths = np.pad(np.diff(x), (0, 1), 'edge')
plt.figure(figsize=(20, 10))
plt.xlabel('D/Dmax')
plt.bar(x, y, width=widths, align='edge', tick_label=x, color='red', edgecolor="black")
plt.show()
In this case, a logical extension for x could be to include 1:
from matplotlib import pyplot as plt
import numpy as np
x = np.array([0., 0.08, 0.12, 0.18, 0.27, 0.42, 0.65])
x = np.concatenate([x, [1]])
y = np.array([0., 0.03758546, 0.06577713, 0.48786205, 0.28553257, 0.09909356, 0.02414922])
widths = np.diff(x)
plt.figure(figsize=(20, 10))
plt.xlabel('D/Dmax')
plt.bar(x[:-1], y, width=widths, align='edge', color='red', edgecolor="black")
plt.xticks(x)
plt.show()
Your real x-values are much smaller than the default bar width which makes the bars overlap. You need to use a smaller bar width, for ex. 0.02 which is of the order of your smaller x-value.
plt.bar(x, y, align='edge', tick_label=x, color='red', edgecolor="black",
width=0.02)

Why setting fixed colorbar failed in this case?

I am trying to make a bunch of polar view plots using the same colorbar. However, the colorbars differ after setting the plotting limits. In the code snippet below, I randomly created 5 maps but plotted in a fixed range, but the output figures are still different in colorbar.
from numpy import linspace, pi, ndarray, random
import matplotlib
matplotlib.use('Agg')
from matplotlib.pyplot import figure
lon = linspace(start=0, stop=2*pi, num=100)
colat = linspace(start=0, stop=9, num=10)
emission = ndarray(shape=(10, 100, 5), dtype=float)
for t in range(5):
emission[:, :, t] = random.rand(10, 100)
fig = figure(num='emission', figsize=(15, 15))
em_pos = [0.05, 0.1, 0.8, 0.8]
emc_pos = [0.9, 0.1, 0.05, 0.8]
for t in range(5):
fig.clear()
ax = fig.add_subplot(121, polar=True, position=em_pos)
axcont = ax.contourf(lon, colat, emission[:, :, t], vmin=0, vmax=2)
axc = fig.add_subplot(122, position=emc_pos)
fig.colorbar(mappable=axcont, cax=axc)
fig.savefig(fname='emission{0:d}.png'.format(t), format='png')
The problem seems to be solved. It is not a problem of colorbar, but a problem of contourf. When I replaced
ax.contourf(lon, colat, emission[:, :, t], vmin=0, vmax=2)
with
ax.pcolormesh(lon, colat, emission[:, :, t], vmin=0, vmax=2)
Then the colorbar shows the proper range. Indeed it is not a full solution, pcolormesh differs in some aspects from contourf, but it meets my needs.