Colorbar tick labels as log outputs - python

I am playing around with histogram2d and I am trying incorporate a color bar logarithmic values.
Here is my current code:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
from matplotlib.colors import LinearSegmentedColormap
cmap = LinearSegmentedColormap.from_list('mycmap', ['black', 'maroon',
'crimson', 'orange', 'white'])
fig = plt.figure()
ax = fig.add_subplot(111)
H = ax.hist2d(gas_pos[:,0]/0.7, gas_pos[:,1]/0.7, cmap=cmap,
norm=matplotlib.colors.LogNorm(), bins=350, weights=np.log(gas_Temp))
ax.tick_params(axis=u'both', which=u'both',length=0)
ax.get_xaxis().set_visible(False)
ax.get_yaxis().set_visible(False)
cb = fig.colorbar(H[3], ax=ax, shrink=0.8, pad=0.01,
orientation="horizontal", label=r'$\log T\ [\mathrm{K}]$')
cb.ax.set_xticklabels([1,2,3,4])
cb.update_ticks()
empty = Rectangle((0,0 ), 0, 0, alpha=0.0)
redshift = fig.legend([empty], [r'$z = 127$'],
loc='upper right', frameon=False, handlelength=0, handletextpad=0)
redshift.get_texts()[0].set_color('white')
#fig.add_artist(redshift)
plt.show()
The weights are values not passed through np.log() and are currently being normalized through LogNorm().
What I am trying to get is to have the colorbar tic labels to be the logarithmic values of what is currently there eg. 10**4 --> 4, 10**6 --> 6, etc.
I have tried changing the formatting and also passing through the logarithmic values of np.log(gas_Temp), but nothing is really working.

The idiomatic thing to do is use a LogFormatterExponent to do the formatting of your colorbar. That's exactly what you need: to display 10**x values as x, or in other words, to display y values as log10(x).
Proof using dummy data:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import LogNorm
from matplotlib.ticker import LogFormatterExponent # <-- one new import here
# generate dummy data
histdata = 10**(np.random.rand(200,200)*4 + 1) # 10^1 -> 10^5
# plot
fig = plt.figure()
ax = fig.add_subplot(111)
ax.tick_params(axis=u'both', which=u'both',length=0)
ax.get_xaxis().set_visible(False)
ax.get_yaxis().set_visible(False)
im = plt.imshow(histdata,cmap='viridis',norm=LogNorm())
cb = fig.colorbar(im, ax=ax, shrink=0.8, pad=0.01,
orientation="horizontal", label=r'$\log T\ [\mathrm{K}]$')
# v-- one new line here
cb.formatter = LogFormatterExponent(base=10) # 10 is the default
cb.update_ticks()
Compare the result of your original (left) with the modified version (right):

Related

Contourf not showing full range of values

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
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.
Solution is to increase the dpi of the figure (matplotlib default is 100dpi):
Note how the reduced points from your example plotted on the left hand side looks the same but the right hand side only looks similar >=300 dpi.
Code:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.cm import get_cmap
import numpy as np
random = np.random.random((360,1600))*2.-1.
clevs = [-0.5,-0.1,0.1,0.5]
f1, ax = plt.subplots(1,2,figsize=(15,5)) # ,dpi=100
diffplot = ax[0].contourf(random[::10,::10],clevs,extend='both')
cbar = f1.colorbar(diffplot,ax=ax[0])
diffplot2 = ax[1].contourf(random[:,:],clevs,extend='both')
cbar = f1.colorbar(diffplot2,ax=ax[1])
f1.set_dpi(100)
f1.suptitle('dpi = 100', fontweight = 'bold', fontsize = 20)
f2, ax = plt.subplots(1,2,figsize=(15,5)) # ,dpi=150
diffplot = ax[0].contourf(random[::10,::10],clevs,extend='both')
cbar = f2.colorbar(diffplot,ax=ax[0])
diffplot2 = ax[1].contourf(random[:,:],clevs,extend='both')
cbar = f2.colorbar(diffplot2,ax=ax[1])
f2.set_dpi(150)
f2.suptitle('dpi = 150', fontweight = 'bold', fontsize = 20)
f3, ax = plt.subplots(1,2,figsize=(15,5)) # ,dpi=300
diffplot = ax[0].contourf(random[::10,::10],clevs,extend='both')
cbar = f3.colorbar(diffplot,ax=ax[0])
diffplot2 = ax[1].contourf(random[:,:],clevs,extend='both')
cbar = f3.colorbar(diffplot2,ax=ax[1])
f3.set_dpi(300)
f3.suptitle('dpi = 300', fontweight = 'bold', fontsize = 20)
f4, ax = plt.subplots(1,2,figsize=(15,5)) # ,dpi=600
diffplot = ax[0].contourf(random[::10,::10],clevs,extend='both')
cbar = f4.colorbar(diffplot,ax=ax[0])
diffplot2 = ax[1].contourf(random[:,:],clevs,extend='both')
cbar = f4.colorbar(diffplot2,ax=ax[1])
f4.set_dpi(600)
f4.suptitle('dpi = 600', fontweight = 'bold', fontsize = 20)
f5, ax = plt.subplots(1,2,figsize=(15,5)) # ,dpi=900
diffplot = ax[0].contourf(random[::10,::10],clevs,extend='both')
cbar = f5.colorbar(diffplot,ax=ax[0])
diffplot2 = ax[1].contourf(random[:,:],clevs,extend='both')
cbar = f5.colorbar(diffplot2,ax=ax[1])
f5.set_dpi(900)
f5.suptitle('dpi = 900', fontweight = 'bold', fontsize = 20)
plt.show()
Note the two options for setting the dpi:
fig, ax = plt.subplots(1,2,figsize=(15,5), dpi=600)
# or
fig.set_dpi(600)
An explanation from another angle - 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.
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.
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.
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()
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.

numpy array 2d showing in Ellipse Demo

I have an numpy 2d array want to show it inside Ellipse Demo
the first line is the color and the second is the sum of feeling which is the third line
xz = np.array([['E6C637', '1692', 'well'],
['7EC31B', '1386', 'free'],
['595884', '1032', 'alone'],
['40B6B8', '905', 'comfortable'],
['99D013', '687', 'fine']])
inside this code:
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.patches import Ellipse
NUM = 250
ells = [Ellipse(xy=np.random.rand(2) * 10,
width=np.random.rand(), height=np.random.rand(),
angle=np.random.rand() * 360)
for i in range(NUM)]
fig, ax = plt.subplots(subplot_kw={'aspect': 'equal'})
for e in ells:
ax.add_artist(e)
e.set_clip_box(ax.bbox)
e.set_alpha(np.random.rand())
e.set_facecolor(np.random.rand(3))
ax.set_xlim(0, 10)
ax.set_ylim(0, 10)
plt.show()
The following code shows the given data as a pie chart:
import matplotlib.pyplot as plt
import numpy as np
xz = [['E6C637', '1692', 'well'],
['7EC31B', '1386', 'free'],
['595884', '1032', 'alone'],
['40B6B8', '905', 'comfortable'],
['99D013', '687', 'fine']]
labels = [data[2] for data in xz]
sizes = [float(data[1]) for data in xz]
colors = ['#'+data[0] for data in xz]
fig1, ax1 = plt.subplots()
ax1.pie(sizes, labels=labels, colors=colors,
autopct='%1.1f%%', startangle=90)
ax1.axis('equal') # Equal aspect ratio ensures that pie is drawn as a circle.
# plt.legend() # show a legend
plt.show()

Plot three images horizontally in python

I'm trying to plot three images with their subtitles above them but nothing is showing right now.
import cv2
from matplotlib import pyplot as plt
img = cv2.imread('kids.tif')
# Averaged environment
avg_blur = cv2.blur(img,(5,5))
# Gaussian filter
gauss_blur = cv2.GaussianBlur(img,(0,0),5)
# Median filter
median_blur = cv2.medianBlur(img,5)
f, axarr = plt.subplots(nrows=1,ncols=3)
axarr[0].imshow(img)
axarr[1].imshow(avg_blur)
axarr[2].imshow(gauss_blur)
"""
plt.subplot(121),plt.imshow(img),plt.title('Original')
plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(avg_blur),plt.title('Averaged environment')
plt.xticks([]), plt.yticks([])
plt.show()
"""
Solution
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
%config InlineBackend.figure_format = 'svg' # 'svg', 'retina'
plt.style.use('seaborn-white')
# Make dummy data for the image
a = np.arange(25**2).reshape(25,25)
# Show subplots | shape: (1,3)
fig, axs = plt.subplots(nrows=1, ncols=3, figsize=(12,4))
for i, ax in enumerate(axs.flatten()):
plt.sca(ax)
plt.imshow(a**(i+1), cmap=plt.cm.jet)
#plt.colorbar()
plt.title('Image: {}'.format(i+1))
#plt.tight_layout()
plt.suptitle('Overall Title')
plt.show()
Output:
Code Similar to Your Implementation
It is easier to just set the current axis plt.sca(ax) and then plot as if you have just one subplot to deal with. This makes your code easy to modify and if need be, swiftly move into a loop.
f, axarr = plt.subplots(nrows=1,ncols=3)
plt.sca(axarr[0]);
plt.imshow(img); plt.title('title 1')
plt.sca(axarr[1]);
plt.imshow(avg_blur); plt.title('title 2')
plt.sca(axarr[2]);
plt.imshow(gauss_blur); plt.title('title 3')
plt.show()
References
https://matplotlib.org/3.1.1/api/_as_gen/matplotlib.pyplot.subplot.html

Matplotlib: Set manual x-axis labels given string label array input, but only on major ticks

I tried this but the labels are not printing in the right location. Some of the labels are not printed and are not printed in the right position.
I have an array of labels that correspond to each data point. I only want some of the labels to be printed and printed only on major ticks. But I do not know how to set major ticks and still keep the labels in correct positions.
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
fig, ax1 = plt.subplots(1, 1)
top = np.arange(100)
btm = top-2
x = np.arange(len(top))
ax1.vlines(x, top, btm, color='r', linewidth=1)
labels = np.linspace(200,300,100).astype(np.int).astype(np.str)
factor = 10
labels = [label for i,label in enumerate(labels) if ((i+1)%factor==1)]
plt.xticks(x, labels, rotation='horizontal')
from matplotlib.ticker import MultipleLocator, FormatStrFormatter, FixedFormatter
majorLocator = MultipleLocator(factor)
majorFormatter = FixedFormatter(labels)
minorLocator = MultipleLocator(1)
ax1.xaxis.set_minor_locator(minorLocator)
ax1.xaxis.set_major_formatter(majorFormatter)
ax1.xaxis.set_major_locator(majorLocator)
plt.tick_params(axis='both', which='major', labelsize=9, length=10)
plt.tick_params(axis='both', which='minor', labelsize=5, length=4)
Help. Thanks.
EDIT:
The labels array is of the same length as the number of data points, which is equal to the length of the x axis. So for every increment in position of the x-axis I have the corresponding label. So for the ith position or tick on the x-axis should have either an empty label or the label equal to ith element of label array. It should be empty if it does not fall on a major tick. The labels are not simply integers, but strings. To be more specific, they are datetime strings.
Without a clear problem description, I need to guess that the following might be what you want:
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from matplotlib.ticker import MultipleLocator
fig, ax1 = plt.subplots(1, 1)
top = np.arange(100)
btm = top-2
x = np.arange(len(top))
ax1.vlines(x+200, top, btm, color='r', linewidth=1)
majorLocator = MultipleLocator(10)
minorLocator = MultipleLocator(1)
ax1.xaxis.set_major_locator(majorLocator)
ax1.xaxis.set_minor_locator(minorLocator)
plt.tick_params(axis='both', which='major', labelsize=9, length=10)
plt.tick_params(axis='both', which='minor', labelsize=5, length=4)
plt.show()
You can also use a FuncFormatter for the ticklabels.
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.ticker import MultipleLocator, FuncFormatter
fig, ax1 = plt.subplots(1, 1)
top = np.arange(100)
btm = top-2
x = np.arange(len(top))
ax1.vlines(x, top, btm, color='r', linewidth=1)
majorLocator = MultipleLocator(10)
minorLocator = MultipleLocator(1)
ax1.xaxis.set_major_locator(majorLocator)
ax1.xaxis.set_minor_locator(minorLocator)
fmt = lambda x,pos : str(int(x+200))
ax1.xaxis.set_major_formatter(FuncFormatter(fmt))
plt.tick_params(axis='both', which='major', labelsize=9, length=10)
plt.tick_params(axis='both', which='minor', labelsize=5, length=4)
plt.show()
What I needed was the FixedLocator with the FixedFormatter, and also an array of integers, majorpos, which specify the indices where the major ticks are located.
The other answer using FuncFormatter would introduce some problems.
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
fig, ax1 = plt.subplots(1, 1)
top = np.arange(100)
btm = top-2
x = np.arange(len(top))
ax1.vlines(x, top, btm, color='r', linewidth=1)
labels = np.linspace(200,300,100).astype(np.int).astype(np.str)
print(labels)
factor = 10
plt.xticks(x, labels, rotation='horizontal')
from matplotlib.ticker import MultipleLocator, FormatStrFormatter, FixedFormatter, FixedLocator
majorpos = np.arange(0,len(labels),int(len(labels)/10))
ax1.xaxis.set_major_locator(FixedLocator((majorpos)))
ax1.xaxis.set_major_formatter(FixedFormatter((labels[majorpos])))
ax1.xaxis.set_minor_locator(MultipleLocator(1))
plt.tick_params(axis='both', which='major', labelsize=9, length=10)
plt.tick_params(axis='both', which='minor', labelsize=5, length=4)

Matplotlib: Add colorbar to non-mappable object

I have a series of lines representing the change of a variable; each with a unique color. For that reason I want to add a colorbar next to the plot. The desired output is shown below.
The problem is that plot is a non-mappable object, i.e. the colorbar has to be added manually. I consider my current solution (below) sub-optimal as it involves size parameters of which I have no interest in controlling. I'd prefer a similar solution as for a mappable object (example below current solution).
Desired output
Current solution
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
x = np.linspace(0, 5, 100)
N = 20
cmap = plt.get_cmap('jet',N)
fig = plt.figure(figsize=(8,6))
ax1 = fig.add_axes([0.10,0.10,0.70,0.85])
for i,n in enumerate(np.linspace(0,2,N)):
y = np.sin(x)*x**n
ax1.plot(x,y,c=cmap(i))
plt.xlabel('x')
plt.ylabel('y')
ax2 = fig.add_axes([0.85,0.10,0.05,0.85])
norm = mpl.colors.Normalize(vmin=0,vmax=2)
cb1 = mpl.colorbar.ColorbarBase(ax2,cmap=cmap,norm=norm,orientation='vertical')
plt.show()
Desired solution
(obviously replacing imshow)
fig,ax = plt.subplots()
cax = ax.imshow(..)
cbar = fig.colorbar(cax,aspect=10)
plt.show()
You may define your own ScalarMappable and use it just as if it was present in the plot.
(Note that I changed the numbero f colors to 21 to have nice spacings of 0.1)
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
x = np.linspace(0, 5, 100)
N = 21
cmap = plt.get_cmap('jet',N)
fig = plt.figure(figsize=(8,6))
ax1 = fig.add_axes([0.10,0.10,0.70,0.85])
for i,n in enumerate(np.linspace(0,2,N)):
y = np.sin(x)*x**n
ax1.plot(x,y,c=cmap(i))
plt.xlabel('x')
plt.ylabel('y')
norm = mpl.colors.Normalize(vmin=0,vmax=2)
sm = plt.cm.ScalarMappable(cmap=cmap, norm=norm)
sm.set_array([])
plt.colorbar(sm, ticks=np.linspace(0,2,N),
boundaries=np.arange(-0.05,2.1,.1))
plt.show()

Categories

Resources