0

I am quite new to python programming. I have a script with me that plots out a heat map using matplotlib. Range of X-axis value = (-180 to +180) and Y-axis value =(0 to 180). The 2D heatmap colours areas in Rainbow according to the number of points occuring in a specified area in the x-y graph (defined by the 'bin' (see below)). In this case, x = values_Rot and y = values_Tilt (see below for code).

As of now, this script colours the 2D-heatmap in the linear scale. How do I change this script such that it colours the heatmap in the log scale? Please note that I only want to change the heatmap colouring scheme to log-scale, i.e. only the number of points in a specified area. The x and y-axis stay the same in linear scale (not in logscale). A portion of the code is here.

rot_number = get_header_number(headers, AngleRot)
tilt_number = get_header_number(headers, AngleTilt)
psi_number = get_header_number(headers, AnglePsi)

values_Rot = []
values_Tilt = []
values_Psi = []

for line in data:
    try:
        values_Rot.append(float(line.split()[rot_number]))
        values_Tilt.append(float(line.split()[tilt_number]))
        values_Psi.append(float(line.split()[psi_number]))

    except:
        print ('This line didnt work, it may just be a blank space. The line is:' + line)

# Change the values here if you want to plot something else, such as psi. 
# You can also change how the data is binned here.
plt.hist2d(values_Rot, values_Tilt, bins=25,)
plt.colorbar()
plt.show()

plt.savefig('name_of_output.png')
Minder
  • 1

1 Answers1

1

You can use a LogNorm for the colors, using plt.hist2d(...., norm=LogNorm()). Here is a comparison.

To have the ticks in base 2, the developers suggest adding the base to the LogLocator and the LogFormatter. As in this case the LogFormatter seems to write the numbers with one decimal (.0), a StrMethodFormatter can be used to show the number without decimals. Depending on the range of numbers, sometimes the minor ticks (shorter marker lines) also get a string, which can be suppressed assigning a NullFormatter for the minor colorbar ticks.

Note that base 2 and base 10 define exactly the same color transformation. The position and the labels of the ticks are different. The example below creates two colorbars to demonstrate the different look.

import matplotlib.pyplot as plt
from matplotlib.ticker import NullFormatter, StrMethodFormatter, LogLocator
from matplotlib.colors import LogNorm
import numpy as np
from copy import copy

# create some toy data for a standalone example
values_Rot = np.random.randn(100, 10).cumsum(axis=1).ravel()
values_Tilt = np.random.randn(100, 10).cumsum(axis=1).ravel()

fig, (ax1, ax2) = plt.subplots(ncols=2, figsize=(15, 4))

cmap = copy(plt.get_cmap('hot'))
cmap.set_bad(cmap(0))

_, _, _, img1 = ax1.hist2d(values_Rot, values_Tilt, bins=40, cmap='hot')
ax1.set_title('Linear norm for the colors')
fig.colorbar(img1, ax=ax1)
_, _, _, img2 = ax2.hist2d(values_Rot, values_Tilt, bins=40, cmap=cmap, norm=LogNorm())
ax2.set_title('Logarithmic norm for the colors')
fig.colorbar(img2, ax=ax2)  # default log 10 colorbar
cbar2 = fig.colorbar(img2, ax=ax2)  # log 2 colorbar
cbar2.ax.yaxis.set_major_locator(LogLocator(base=2))
cbar2.ax.yaxis.set_major_formatter(StrMethodFormatter('{x:.0f}'))
cbar2.ax.yaxis.set_minor_formatter(NullFormatter())
plt.show()

example plot

Note that log(0) is minus infinity. Therefore, the zero values in the left plot (darkest color) are left empty (white background) on the plot with the logarithmic color values. If you just want to use the lowest color for these zeros, you need to set a 'bad' color. In order not the change a standard colormap, the latest matplotlib versions wants you to first make a copy of the colormap.

PS: When calling plt.savefig() it is important to call it before plt.show() because plt.show() clears the plot.

Also, try to avoid the 'jet' colormap, as it has a bright yellow region which is not at the extreme. It may look nice, but can be very misleading. This blog article contains a thorough explanation. The matplotlib documentation contains an overview of available colormaps.

Note that to compare two plots, plt.subplots() needs to be used, and instead of plt.hist2d, ax.hist2d is needed (see this post). Also, with two colorbars, the elements on which the colorbars are based need to be given as parameter. A minimal change to your code would look like:

from matplotlib.ticker import NullFormatter, StrMethodFormatter, LogLocator
from matplotlib.colors import LogNorm
from matplotlib import pyplot as plt
from copy import copy

# ...
# reading the data as before

cmap = copy(plt.get_cmap('magma'))
cmap.set_bad(cmap(0))
plt.hist2d(values_Rot, values_Tilt, bins=25, cmap=cmap, norm=LogNorm())
cbar = plt.colorbar()
cbar.ax.yaxis.set_major_locator(LogLocator(base=2))
cbar.ax.yaxis.set_major_formatter(StrMethodFormatter('{x:.0f}'))
cbar.ax.yaxis.set_minor_formatter(NullFormatter())

plt.savefig('name_of_output.png') # needs to be called prior to plt.show()
plt.show()
JohanC
  • 71,591
  • 8
  • 33
  • 66
  • Thanks - is there a way to change the base for log from base10 to base 2? – Minder Oct 08 '20 at 11:54
  • Thanks for the useful feedback. Your answer does partly answer the question, but how do you change from base 10 to base 2 without modifying my original code heavily. I need to change the base for a specific reason - I have numbers only from (0-9) which is the range on my heatplot. – Minder Oct 09 '20 at 11:01
  • 1
    I updated the answer with the minimal change needed to the code. Don't forget to change 'jet' to a less cumbersome colormap. I also changed the sample values to have a colorbar with small numbers. – JohanC Oct 09 '20 at 15:21