Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 28ccfa9

Browse files
committed
Merge pull request #4499 from jklymak/jklymak-colormap-norm-examp
DOC: normalization example + explanation
2 parents 5259594 + 9f02c33 commit 28ccfa9

9 files changed

+465
-1
lines changed

doc/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
else:
4545
print("Using IPython's ipython_console_highlighting directive")
4646
extensions.append('IPython.sphinxext.ipython_console_highlighting')
47-
47+
extensions.append('IPython.sphinxext.ipython_directive')
4848
try:
4949
import numpydoc
5050
except ImportError:

doc/users/beginner.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ Beginner's Guide
2121
annotations_guide.rst
2222
screenshots.rst
2323
colormaps.rst
24+
colormapnorms.rst
2425

2526

2627

doc/users/colormapnorms.rst

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
.. _colormapnorm-tutorial:
2+
3+
Colormap Normaliztions
4+
================================
5+
6+
Objects that use colormaps by default linearly map the colors in the
7+
colormap from data values *vmin* to *vmax*. For example::
8+
9+
pcm = ax.pcolormesh(x, y, Z, vmin=-1., vmax=1., cmap='RdBu_r')
10+
11+
will map the data in *Z* linearly from -1 to +1, so *Z=0* will
12+
give a color at the center of the colormap *RdBu_r* (white in this
13+
case).
14+
15+
Matplotlib does this mapping in two steps, with a normalization from
16+
[0,1] occuring first, and then mapping onto the indices in the
17+
colormap. Normalizations are defined as part of
18+
:func:`matplotlib.colors` module. The default normalization is
19+
:func:`matplotlib.colors.Normalize`.
20+
21+
The artists that map data to
22+
color pass the arguments *vmin* and *vmax* to
23+
:func:`matplotlib.colors.Normalize`. We can substnatiate the
24+
normalization and see what it returns. In this case it returns 0.5:
25+
26+
.. ipython::
27+
28+
In [1]: import matplotlib as mpl
29+
30+
In [2]: norm=mpl.colors.Normalize(vmin=-1.,vmax=1.)
31+
32+
In [3]: norm(0.)
33+
Out[3]: 0.5
34+
35+
However, there are sometimes cases where it is useful to map data to
36+
colormaps in a non-linear fashion.
37+
38+
Logarithmic
39+
---------------------------------
40+
41+
One of the most common transformations is to plot data by taking its
42+
logarithm (to the base-10). This transformation is useful when there
43+
are changes across disparate scales that we still want to be able to
44+
see. Using :func:`colors.LogNorm` normalizes the data by
45+
:math:`log_{10}`. In the example below, there are two bumps, one much
46+
smaller than the other. Using :func:`colors.LogNorm`, the shape and
47+
location of each bump can clearly be seen:
48+
49+
.. plot:: users/plotting/examples/colormap_normalizations_lognorm.py
50+
:include-source:
51+
52+
Symetric logarithmic
53+
---------------------------------
54+
55+
Similarly, it sometimes happens that there is data that is positive
56+
and negative, but we would still like a logarithmic scaling applied to
57+
both. In this case, the negative numbers are also scaled
58+
logarithmically, and mapped to small numbers. i.e. If `vmin=-vmax`,
59+
then they the negative numbers are mapped from 0 to 0.5 and the
60+
positive from 0.5 to 1.
61+
62+
Since the values close to zero tend toward infinity, there is a need
63+
to have a range around zero that is linear. The parameter *linthresh*
64+
allows the user to specify the size of this range (-*linthresh*,
65+
*linthresh*). The size of this range in the colormap is set by
66+
*linscale*. When *linscale* == 1.0 (the default), the space used for
67+
the positive and negative halves of the linear range will be equal to
68+
one decade in the logarithmic range.
69+
70+
.. plot:: users/plotting/examples/colormap_normalizations_symlognorm.py
71+
:include-source:
72+
73+
Power-law
74+
---------------------------------
75+
76+
Sometimes it is useful to remap the colors onto a power-law
77+
relationship (i.e. :math:`y=x^{\gamma}`, where :math:`\gamma` is the
78+
power). For this we use the :func:`colors.PowerNorm`. It takes as an
79+
argument *gamma* ( *gamma* == 1.0 will just yield the defalut linear
80+
normalization):
81+
82+
.. note::
83+
84+
There should probably be a good reason for plotting the data using
85+
this type of transformation. Technical viewers are used to linear
86+
and logarithmic axes and data transformations. Power laws are less
87+
common, and viewers should explictly be made aware that they have
88+
been used.
89+
90+
91+
.. plot:: users/plotting/examples/colormap_normalizations_power.py
92+
:include-source:
93+
94+
Discrete bounds
95+
---------------------------------
96+
97+
Another normaization that comes with matplolib is
98+
:func:`colors.BoundaryNorm`. In addition to *vmin* and *vmax*, this
99+
takes as arguments boundaries between which data is to be mapped. The
100+
colors are then linearly distributed between these "bounds". For
101+
instance, if:
102+
103+
.. ipython::
104+
105+
In [2]: import matplotlib.colors as colors
106+
107+
In [3]: bounds = np.array([-0.25, -0.125, 0, 0.5, 1])
108+
109+
In [4]: norm = colors.BoundaryNorm(boundaries=bounds, ncolors=4)
110+
111+
In [5]: print(norm([-0.2,-0.15,-0.02, 0.3, 0.8, 0.99]))
112+
[0 0 1 2 3 3]
113+
114+
Note unlike the other norms, this norm returns values from 0 to *ncolors*-1.
115+
116+
.. plot:: users/plotting/examples/colormap_normalizations_bounds.py
117+
:include-source:
118+
119+
120+
Custom normalization: Two linear ranges
121+
-----------------------------------------
122+
123+
It is possible to define your own normalization. This example
124+
plots the same data as the :func:`colors:SymLogNorm` example, but
125+
a different linear map is used for the negative data values than
126+
the positive. (Note that this example is simple, and does not account
127+
for the edge cases like masked data or invalid values of *vmin* and
128+
*vmax*)
129+
130+
.. note::
131+
This may appear soon as :func:`colors.OffsetNorm`
132+
133+
As above, non-symetric mapping of data to color is non-standard
134+
practice for quantitative data, and should only be used advisedly. A
135+
practical example is having an ocean/land colormap where the land and
136+
ocean data span different ranges.
137+
138+
.. plot:: users/plotting/examples/colormap_normalizations_custom.py
139+
:include-source:
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
"""
2+
Demonstration of using norm to map colormaps onto data in non-linear ways.
3+
"""
4+
5+
import numpy as np
6+
import matplotlib.pyplot as plt
7+
import matplotlib.colors as colors
8+
from matplotlib.mlab import bivariate_normal
9+
10+
'''
11+
Lognorm: Instead of pcolor log10(Z1) you can have colorbars that have
12+
the exponential labels using a norm.
13+
'''
14+
N = 100
15+
X, Y = np.mgrid[-3:3:complex(0, N), -2:2:complex(0, N)]
16+
17+
# A low hump with a spike coming out of the top right. Needs to have
18+
# z/colour axis on a log scale so we see both hump and spike. linear
19+
# scale only shows the spike.
20+
Z1 = bivariate_normal(X, Y, 0.1, 0.2, 1.0, 1.0) + \
21+
0.1 * bivariate_normal(X, Y, 1.0, 1.0, 0.0, 0.0)
22+
23+
fig, ax = plt.subplots(2, 1)
24+
25+
pcm = ax[0].pcolor(X, Y, Z1,
26+
norm=colors.LogNorm(vmin=Z1.min(), vmax=Z1.max()),
27+
cmap='PuBu_r')
28+
fig.colorbar(pcm, ax=ax[0], extend='max')
29+
30+
pcm = ax[1].pcolor(X, Y, Z1, cmap='PuBu_r')
31+
fig.colorbar(pcm, ax=ax[1], extend='max')
32+
fig.show()
33+
34+
35+
'''
36+
PowerNorm: Here a power-law trend in X partially obscures a rectified
37+
sine wave in Y. We can remove the power law using a PowerNorm.
38+
'''
39+
X, Y = np.mgrid[0:3:complex(0, N), 0:2:complex(0, N)]
40+
Z1 = (1 + np.sin(Y * 10.)) * X**(2.)
41+
42+
fig, ax = plt.subplots(2, 1)
43+
44+
pcm = ax[0].pcolormesh(X, Y, Z1, norm=colors.PowerNorm(gamma=1./2.),
45+
cmap='PuBu_r')
46+
fig.colorbar(pcm, ax=ax[0], extend='max')
47+
48+
pcm = ax[1].pcolormesh(X, Y, Z1, cmap='PuBu_r')
49+
fig.colorbar(pcm, ax=ax[1], extend='max')
50+
fig.show()
51+
52+
'''
53+
SymLogNorm: two humps, one negative and one positive, The positive
54+
with 5-times the amplitude. Linearly, you cannot see detail in the
55+
negative hump. Here we logarithmically scale the positive and
56+
negative data separately.
57+
58+
Note that colorbar labels do not come out looking very good.
59+
'''
60+
61+
X, Y = np.mgrid[-3:3:complex(0, N), -2:2:complex(0, N)]
62+
Z1 = (bivariate_normal(X, Y, 1., 1., 1.0, 1.0))**2 \
63+
- 0.4 * (bivariate_normal(X, Y, 1.0, 1.0, -1.0, 0.0))**2
64+
Z1 = Z1/0.03
65+
66+
fig, ax = plt.subplots(2, 1)
67+
68+
pcm = ax[0].pcolormesh(X, Y, Z1,
69+
norm=colors.SymLogNorm(linthresh=0.03, linscale=0.03,
70+
vmin=-1.0, vmax=1.0),
71+
cmap='RdBu_r')
72+
fig.colorbar(pcm, ax=ax[0], extend='both')
73+
74+
pcm = ax[1].pcolormesh(X, Y, Z1, cmap='RdBu_r', vmin=-np.max(Z1))
75+
fig.colorbar(pcm, ax=ax[1], extend='both')
76+
fig.show()
77+
78+
79+
'''
80+
Custom Norm: An example with a customized normalization. This one
81+
uses the example above, and normalizes the negative data differently
82+
from the positive.
83+
'''
84+
X, Y = np.mgrid[-3:3:complex(0, N), -2:2:complex(0, N)]
85+
Z1 = (bivariate_normal(X, Y, 1., 1., 1.0, 1.0))**2 \
86+
- 0.4 * (bivariate_normal(X, Y, 1.0, 1.0, -1.0, 0.0))**2
87+
Z1 = Z1/0.03
88+
89+
# Example of making your own norm. Also see matplotlib.colors.
90+
# From Joe Kington: This one gives two different linear ramps:
91+
92+
93+
class MidpointNormalize(colors.Normalize):
94+
def __init__(self, vmin=None, vmax=None, midpoint=None, clip=False):
95+
self.midpoint = midpoint
96+
colors.Normalize.__init__(self, vmin, vmax, clip)
97+
98+
def __call__(self, value, clip=None):
99+
# I'm ignoring masked values and all kinds of edge cases to make a
100+
# simple example...
101+
x, y = [self.vmin, self.midpoint, self.vmax], [0, 0.5, 1]
102+
return np.ma.masked_array(np.interp(value, x, y))
103+
#####
104+
fig, ax = plt.subplots(2, 1)
105+
106+
pcm = ax[0].pcolormesh(X, Y, Z1,
107+
norm=MidpointNormalize(midpoint=0.),
108+
cmap='RdBu_r')
109+
fig.colorbar(pcm, ax=ax[0], extend='both')
110+
111+
pcm = ax[1].pcolormesh(X, Y, Z1, cmap='RdBu_r', vmin=-np.max(Z1))
112+
fig.colorbar(pcm, ax=ax[1], extend='both')
113+
fig.show()
114+
115+
'''
116+
BoundaryNorm: For this one you provide the boundaries for your colors,
117+
and the Norm puts the first color in between the first pair, the
118+
second color between the second pair, etc.
119+
'''
120+
121+
fig, ax = plt.subplots(3, 1, figsize=(8, 8))
122+
ax = ax.flatten()
123+
# even bounds gives a contour-like effect
124+
bounds = np.linspace(-1, 1, 10)
125+
norm = colors.BoundaryNorm(boundaries=bounds, ncolors=256)
126+
pcm = ax[0].pcolormesh(X, Y, Z1,
127+
norm=norm,
128+
cmap='RdBu_r')
129+
fig.colorbar(pcm, ax=ax[0], extend='both', orientation='vertical')
130+
131+
# uneven bounds changes the colormapping:
132+
bounds = np.array([-0.25, -0.125, 0, 0.5, 1])
133+
norm = colors.BoundaryNorm(boundaries=bounds, ncolors=256)
134+
pcm = ax[1].pcolormesh(X, Y, Z1, norm=norm, cmap='RdBu_r')
135+
fig.colorbar(pcm, ax=ax[1], extend='both', orientation='vertical')
136+
137+
pcm = ax[2].pcolormesh(X, Y, Z1, cmap='RdBu_r', vmin=-np.max(Z1))
138+
fig.colorbar(pcm, ax=ax[2], extend='both', orientation='vertical')
139+
fig.show()
140+
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
"""
2+
Demonstration of using norm to map colormaps onto data in non-linear ways.
3+
"""
4+
5+
import numpy as np
6+
import matplotlib.pyplot as plt
7+
import matplotlib.colors as colors
8+
from matplotlib.mlab import bivariate_normal
9+
10+
N = 100
11+
X, Y = np.mgrid[-3:3:complex(0, N), -2:2:complex(0, N)]
12+
Z1 = (bivariate_normal(X, Y, 1., 1., 1.0, 1.0))**2 \
13+
- 0.4 * (bivariate_normal(X, Y, 1.0, 1.0, -1.0, 0.0))**2
14+
Z1 = Z1/0.03
15+
16+
'''
17+
BoundaryNorm: For this one you provide the boundaries for your colors,
18+
and the Norm puts the first color in between the first pair, the
19+
second color between the second pair, etc.
20+
'''
21+
22+
fig, ax = plt.subplots(3, 1, figsize=(8, 8))
23+
ax = ax.flatten()
24+
# even bounds gives a contour-like effect
25+
bounds = np.linspace(-1, 1, 10)
26+
norm = colors.BoundaryNorm(boundaries=bounds, ncolors=256)
27+
pcm = ax[0].pcolormesh(X, Y, Z1,
28+
norm=norm,
29+
cmap='RdBu_r')
30+
fig.colorbar(pcm, ax=ax[0], extend='both', orientation='vertical')
31+
32+
# uneven bounds changes the colormapping:
33+
bounds = np.array([-0.25, -0.125, 0, 0.5, 1])
34+
norm = colors.BoundaryNorm(boundaries=bounds, ncolors=256)
35+
pcm = ax[1].pcolormesh(X, Y, Z1, norm=norm, cmap='RdBu_r')
36+
fig.colorbar(pcm, ax=ax[1], extend='both', orientation='vertical')
37+
38+
pcm = ax[2].pcolormesh(X, Y, Z1, cmap='RdBu_r', vmin=-np.max(Z1))
39+
fig.colorbar(pcm, ax=ax[2], extend='both', orientation='vertical')
40+
fig.show()
41+
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
"""
2+
Demonstration of using norm to map colormaps onto data in non-linear ways.
3+
"""
4+
5+
import numpy as np
6+
import matplotlib.pyplot as plt
7+
import matplotlib.colors as colors
8+
from matplotlib.mlab import bivariate_normal
9+
10+
N = 100
11+
'''
12+
Custom Norm: An example with a customized normalization. This one
13+
uses the example above, and normalizes the negative data differently
14+
from the positive.
15+
'''
16+
X, Y = np.mgrid[-3:3:complex(0, N), -2:2:complex(0, N)]
17+
Z1 = (bivariate_normal(X, Y, 1., 1., 1.0, 1.0))**2 \
18+
- 0.4 * (bivariate_normal(X, Y, 1.0, 1.0, -1.0, 0.0))**2
19+
Z1 = Z1/0.03
20+
21+
# Example of making your own norm. Also see matplotlib.colors.
22+
# From Joe Kington: This one gives two different linear ramps:
23+
24+
25+
class MidpointNormalize(colors.Normalize):
26+
def __init__(self, vmin=None, vmax=None, midpoint=None, clip=False):
27+
self.midpoint = midpoint
28+
colors.Normalize.__init__(self, vmin, vmax, clip)
29+
30+
def __call__(self, value, clip=None):
31+
# I'm ignoring masked values and all kinds of edge cases to make a
32+
# simple example...
33+
x, y = [self.vmin, self.midpoint, self.vmax], [0, 0.5, 1]
34+
return np.ma.masked_array(np.interp(value, x, y))
35+
#####
36+
fig, ax = plt.subplots(2, 1)
37+
38+
pcm = ax[0].pcolormesh(X, Y, Z1,
39+
norm=MidpointNormalize(midpoint=0.),
40+
cmap='RdBu_r')
41+
fig.colorbar(pcm, ax=ax[0], extend='both')
42+
43+
pcm = ax[1].pcolormesh(X, Y, Z1, cmap='RdBu_r', vmin=-np.max(Z1))
44+
fig.colorbar(pcm, ax=ax[1], extend='both')
45+
fig.show()
46+

0 commit comments

Comments
 (0)