{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "%matplotlib inline" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\nColormap Normalization\n======================\n\nObjects that use colormaps by default linearly map the colors in the\ncolormap from data values *vmin* to *vmax*. For example::\n\n pcm = ax.pcolormesh(x, y, Z, vmin=-1., vmax=1., cmap='RdBu_r')\n\nwill map the data in *Z* linearly from -1 to +1, so *Z=0* will\ngive a color at the center of the colormap *RdBu_r* (white in this\ncase).\n\nMatplotlib does this mapping in two steps, with a normalization from\n[0,1] occurring first, and then mapping onto the indices in the\ncolormap. Normalizations are classes defined in the\n:func:matplotlib.colors module. The default, linear normalization is\n:func:matplotlib.colors.Normalize.\n\nArtists that map data to color pass the arguments *vmin* and *vmax* to\nconstruct a :func:matplotlib.colors.Normalize instance, then call it:\n\n.. ipython::\n\n In : import matplotlib as mpl\n\n In : norm = mpl.colors.Normalize(vmin=-1.,vmax=1.)\n\n In : norm(0.)\n Out: 0.5\n\nHowever, there are sometimes cases where it is useful to map data to\ncolormaps in a non-linear fashion.\n\nLogarithmic\n-----------\n\nOne of the most common transformations is to plot data by taking\nits logarithm (to the base-10). This transformation is useful to\ndisplay changes across disparate scales. Using :func:colors.LogNorm\nnormalizes the data via $log_{10}$. In the example below,\nthere are two bumps, one much smaller than the other. Using\n:func:colors.LogNorm, the shape and location of each bump can clearly\nbe seen:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "import numpy as np\nimport matplotlib.pyplot as plt\nimport matplotlib.colors as colors\nfrom matplotlib.mlab import bivariate_normal\n\nN = 100\nX, Y = np.mgrid[-3:3:complex(0, N), -2:2:complex(0, N)]\n\n# A low hump with a spike coming out of the top right. Needs to have\n# z/colour axis on a log scale so we see both hump and spike. linear\n# scale only shows the spike.\nZ1 = bivariate_normal(X, Y, 0.1, 0.2, 1.0, 1.0) + \\\n 0.1 * bivariate_normal(X, Y, 1.0, 1.0, 0.0, 0.0)\n\nfig, ax = plt.subplots(2, 1)\n\npcm = ax.pcolor(X, Y, Z1,\n norm=colors.LogNorm(vmin=Z1.min(), vmax=Z1.max()),\n cmap='PuBu_r')\nfig.colorbar(pcm, ax=ax, extend='max')\n\npcm = ax.pcolor(X, Y, Z1, cmap='PuBu_r')\nfig.colorbar(pcm, ax=ax, extend='max')\nfig.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Symmetric logarithmic\n---------------------\n\nSimilarly, it sometimes happens that there is data that is positive\nand negative, but we would still like a logarithmic scaling applied to\nboth. In this case, the negative numbers are also scaled\nlogarithmically, and mapped to smaller numbers; e.g., if vmin=-vmax,\nthen they the negative numbers are mapped from 0 to 0.5 and the\npositive from 0.5 to 1.\n\nSince the logarithm of values close to zero tends toward infinity, a\nsmall range around zero needs to be mapped linearly. The parameter\n*linthresh* allows the user to specify the size of this range\n(-*linthresh*, *linthresh*). The size of this range in the colormap is\nset by *linscale*. When *linscale* == 1.0 (the default), the space used\nfor the positive and negative halves of the linear range will be equal\nto one decade in the logarithmic range.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "N = 100\nX, Y = np.mgrid[-3:3:complex(0, N), -2:2:complex(0, N)]\nZ1 = (bivariate_normal(X, Y, 1., 1., 1.0, 1.0))**2 \\\n - 0.4 * (bivariate_normal(X, Y, 1.0, 1.0, -1.0, 0.0))**2\nZ1 = Z1/0.03\n\nfig, ax = plt.subplots(2, 1)\n\npcm = ax.pcolormesh(X, Y, Z1,\n norm=colors.SymLogNorm(linthresh=0.03, linscale=0.03,\n vmin=-1.0, vmax=1.0),\n cmap='RdBu_r')\nfig.colorbar(pcm, ax=ax, extend='both')\n\npcm = ax.pcolormesh(X, Y, Z1, cmap='RdBu_r', vmin=-np.max(Z1))\nfig.colorbar(pcm, ax=ax, extend='both')\nfig.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Power-law\n---------\n\nSometimes it is useful to remap the colors onto a power-law\nrelationship (i.e. $y=x^{\\gamma}$, where $\\gamma$ is the\npower). For this we use the :func:colors.PowerNorm. It takes as an\nargument *gamma* (*gamma* == 1.0 will just yield the default linear\nnormalization):\n\n

#### Note

There should probably be a good reason for plotting the data using\n this type of transformation. Technical viewers are used to linear\n and logarithmic axes and data transformations. Power laws are less\n common, and viewers should explicitly be made aware that they have\n been used.

\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "N = 100\nX, Y = np.mgrid[0:3:complex(0, N), 0:2:complex(0, N)]\nZ1 = (1 + np.sin(Y * 10.)) * X**(2.)\n\nfig, ax = plt.subplots(2, 1)\n\npcm = ax.pcolormesh(X, Y, Z1, norm=colors.PowerNorm(gamma=1./2.),\n cmap='PuBu_r')\nfig.colorbar(pcm, ax=ax, extend='max')\n\npcm = ax.pcolormesh(X, Y, Z1, cmap='PuBu_r')\nfig.colorbar(pcm, ax=ax, extend='max')\nfig.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Discrete bounds\n---------------\n\nAnother normaization that comes with matplolib is\n:func:colors.BoundaryNorm. In addition to *vmin* and *vmax*, this\ntakes as arguments boundaries between which data is to be mapped. The\ncolors are then linearly distributed between these \"bounds\". For\ninstance:\n\n.. ipython::\n\n In : import matplotlib.colors as colors\n\n In : bounds = np.array([-0.25, -0.125, 0, 0.5, 1])\n\n In : norm = colors.BoundaryNorm(boundaries=bounds, ncolors=4)\n\n In : print(norm([-0.2,-0.15,-0.02, 0.3, 0.8, 0.99]))\n [0 0 1 2 3 3]\n\nNote unlike the other norms, this norm returns values from 0 to *ncolors*-1.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "N = 100\nX, Y = np.mgrid[-3:3:complex(0, N), -2:2:complex(0, N)]\nZ1 = (bivariate_normal(X, Y, 1., 1., 1.0, 1.0))**2 \\\n - 0.4 * (bivariate_normal(X, Y, 1.0, 1.0, -1.0, 0.0))**2\nZ1 = Z1/0.03\n\nfig, ax = plt.subplots(3, 1, figsize=(8, 8))\nax = ax.flatten()\n# even bounds gives a contour-like effect\nbounds = np.linspace(-1, 1, 10)\nnorm = colors.BoundaryNorm(boundaries=bounds, ncolors=256)\npcm = ax.pcolormesh(X, Y, Z1,\n norm=norm,\n cmap='RdBu_r')\nfig.colorbar(pcm, ax=ax, extend='both', orientation='vertical')\n\n# uneven bounds changes the colormapping:\nbounds = np.array([-0.25, -0.125, 0, 0.5, 1])\nnorm = colors.BoundaryNorm(boundaries=bounds, ncolors=256)\npcm = ax.pcolormesh(X, Y, Z1, norm=norm, cmap='RdBu_r')\nfig.colorbar(pcm, ax=ax, extend='both', orientation='vertical')\n\npcm = ax.pcolormesh(X, Y, Z1, cmap='RdBu_r', vmin=-np.max(Z1))\nfig.colorbar(pcm, ax=ax, extend='both', orientation='vertical')\nfig.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Custom normalization: Two linear ranges\n---------------------------------------\n\nIt is possible to define your own normalization. In the following\nexample, we modify :func:colors:SymLogNorm to use different linear\nmaps for the negative data values and the positive. (Note that this\nexample is simple, and does not validate inputs or account for complex\ncases such as masked data)\n\n

#### Note

This may appear soon as :func:colors.OffsetNorm.\n\n As above, non-symmetric mapping of data to color is non-standard\n practice for quantitative data, and should only be used advisedly. A\n practical example is having an ocean/land colormap where the land and\n ocean data span different ranges.

\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "N = 100\nX, Y = np.mgrid[-3:3:complex(0, N), -2:2:complex(0, N)]\nZ1 = (bivariate_normal(X, Y, 1., 1., 1.0, 1.0))**2 \\\n - 0.4 * (bivariate_normal(X, Y, 1.0, 1.0, -1.0, 0.0))**2\nZ1 = Z1/0.03\n\n\nclass MidpointNormalize(colors.Normalize):\n def __init__(self, vmin=None, vmax=None, midpoint=None, clip=False):\n self.midpoint = midpoint\n colors.Normalize.__init__(self, vmin, vmax, clip)\n\n def __call__(self, value, clip=None):\n # I'm ignoring masked values and all kinds of edge cases to make a\n # simple example...\n x, y = [self.vmin, self.midpoint, self.vmax], [0, 0.5, 1]\n return np.ma.masked_array(np.interp(value, x, y))\n\nfig, ax = plt.subplots(2, 1)\n\npcm = ax.pcolormesh(X, Y, Z1,\n norm=MidpointNormalize(midpoint=0.),\n cmap='RdBu_r')\nfig.colorbar(pcm, ax=ax, extend='both')\n\npcm = ax.pcolormesh(X, Y, Z1, cmap='RdBu_r', vmin=-np.max(Z1))\nfig.colorbar(pcm, ax=ax, extend='both')\nfig.show()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.4" } }, "nbformat": 4, "nbformat_minor": 0 }