I'm using plr.scatter and logariphmic scale, and i'm trying to add some specific tick values to the colorbar, but it seems to work really arbitrary. See the example:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
import matplotlib
from matplotlib.ticker import LogFormatter
x, y = np.meshgrid(np.linspace(0, 1, 30), np.linspace(0, 1, 30))
z = x**2 + 15*y**3 + 1.5
plt.figure(figsize=(9, 4.5))
plt.scatter(x, y, c=z, cmap=cm.jet, norm=matplotlib.colors.LogNorm(), vmin=1, vmax=20)
formatter = LogFormatter(10, labelOnlyBase=False)
cbar = plt.colorbar(ticks=[1, 2, 5, 10, 15, 20], format=formatter)
This code produced all the required major ticks, plus some minor ticks, but only labeled 1 and 10, while I need all numbers to be seen in colorbar. At first I though it was due to the fact that 1 and 10 are integer powers of 10, and other number are not, but...
...if I change the log base to 2, we can see tick labels at 1 and 2, which are powers of 2, but we also see labels at 5, 10 and 20, which are not. 15 did not appear this time too, but if I try adding 17 it works (not shown on the picture, but it does)
formatter = LogFormatter(2, labelOnlyBase=False)
What is this sorcery and how do I make matplotlib add exactly the labels I want to the ticks? I can do it manually by using
cbar.ax.set_yticklabels(['1', '2', '5', '10', '15', '20'])
but it seems redundant. Is there a better way?
You can format any axis ticks with formatter. Below is the example .
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
import matplotlib
from matplotlib.colors import LogNorm
x, y = np.meshgrid(np.linspace(0, 1, 30), np.linspace(0, 1, 30))
z = x**2 + 15*y**3 + 1.5
f, ax = plt.subplots(figsize=(9, 4.5))
p = plt.scatter(x, y, c=z, cmap=cm.jet, norm=LogNorm(vmin=1, vmax=20) )
v1 = np.linspace(z.min(), z.max(), 8, endpoint=True)
cbar=plt.colorbar(ticks=v1)
cbar.ax.set_yticklabels(["{:4.2f}".format(i) for i in v1]) # add the labels
LogFormatter and its subclasses use the minor_thresholds parameter to decide when to hide non-decade tick labels to prevent overcrowding. By default this will hide nearly all non-decade labels, but you can increase it to allow more labels to appear.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
from matplotlib.ticker import LogFormatter
from matplotlib.colors import LogNorm
x, y = np.meshgrid(np.linspace(0, 1, 30), np.linspace(0, 1, 30))
z = x**2 + 15*y**3 + 1.5
plt.figure(figsize=(9, 4.5))
cnorm = LogNorm(vmin=1, vmax=20)
plt.scatter(x, y, c=z, cmap=cm.jet, norm=cnorm)
# define minor_thresholds to be >= the range of the color scale
decades = np.ceil(np.log10(cnorm.vmax / cnorm.vmin))
formatter = LogFormatter(10, minor_thresholds=(decades, decades))
cbar = plt.colorbar(ticks=[1, 2, 5, 10, 15, 20], format=formatter)
Related
I want to adjust colobar scale from my current figure1 to the desired figure2 !!
My colorbar scale range is -1 to 1, but I want it in exponential form and for that I tried levels = np.linspace(-100e-2,100e-2) as well, but it also doesn't give the desired scale2
import xarray as xr
import numpy as np
import matplotlib.pyplot as plt
ds = xr.open_dataset('PL_Era_Tkt.nc')
wp = ds.w.mean(dim=['longitude','latitude']).plot.contourf(x='time',cmap='RdBu',add_colorbar=False,extend='both')
wpcb = plt.colorbar(wp)
wpcb.set_label(label='Pa.s${^{-1}}$',size=13)
plt.gca().invert_yaxis()
plt.title('Vertical Velocity',size=15)
My current scale
My desired scale
Since the data is not presented, I added normalized color bars with the data from the graph sample here. I think the color bar scales will also be in log format with this setup. Please note that the data used is not large, so I have not been able to confirm this.
%matplotlib inline
import matplotlib.pyplot as plt
import matplotlib as mpl
import matplotlib.ticker as ticker
import numpy as np
plt.style.use('seaborn-white')
def f(x, y):
return np.sin(x) ** 10 + np.cos(10 + y * x) * np.cos(x)
x = np.linspace(0, 5, 50)
y = np.linspace(0, 5, 40)
X, Y = np.meshgrid(x, y)
Z = f(X, Y)
fig, ax = plt.subplots()
ax.contourf(X, Y, Z, 20, cmap='RdGy')
cmap = mpl.cm.RdGy
norm = mpl.colors.Normalize(vmin=-1, vmax=1.0)
fig.colorbar(mpl.cm.ScalarMappable(norm=norm, cmap=cmap),
ax=ax, orientation='vertical', label='Some Units', extend='both', ticks=ticker.LogLocator())
plt.show()
I would like to be able to add footnote text similar to the following in matplotlib:
The following code will create a plot with similar text
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
fig, ax = plt.subplots(figsize = (5, 8))
n = 10
np.random.seed(1)
_ = ax.scatter(np.random.randint(0, 10, n), np.random.randint(0, 10, n), s=500)
x = 0
y = 1
_ = ax.text(
x, y, "hello this is some text at the bottom of the plot", fontsize=15, color="#555"
)
Which looks as:
However, if the data changes then the above won't adjust, such as:
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
fig, ax = plt.subplots(figsize=(5, 8))
n = 10
np.random.seed(2)
_ = ax.scatter(np.random.randint(0, 10, n), np.random.randint(0, 10, n), s=500)
x = 0
y = 1
_ = ax.text(
x, y, "hello this is some text at the bottom of the plot", fontsize=15, color="#555"
)
I have seen this question/answer, and this just says how to plot text at a particular x,y coordinate. I specifically want to be able to set a footnote though, not plot at a particular x,y, so the solution should be dynamic.
Also, use of the OOP interface is preferred as mentioned in the docs.
Note - there seems to be issues with the current suggestion when using fig.tight_layout()
You should try plotting the text relative to the subplot and not relative to the points in the subplot using transform=ax.transAxes. You should also set the alignment so that the text starts based on the location you want. The can play around with the point location.
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
fig, ax = plt.subplots(figsize=(5, 8))
n = 10
np.random.seed(2)
_ = ax.scatter(np.random.randint(0, 10, n), np.random.randint(0, 10, n), s=500)
x = 0
y = -.07
ax.text(x, y, "hello this is some text at the bottom of the plot", fontsize=15,
horizontalalignment='left',verticalalignment='top', transform=ax.transAxes)
plt.show()
I'm trying to get an "inverse" second x-axis to a x-axis in log-scale. secondary_xaxis works fine but I can't format the ticks as usually. Is this a bug or am I missing something?
Here's what I do:
import matplotlib.pyplot as plt
import numpy as np
fig, axs = plt.subplots(figsize=(0.6*4,0.6*3))
y = np.random.rand(10)
x = np.linspace(30,200,num=10)
p = plt.plot(x, y, marker='o', markersize=3)
plt.xscale("log")
def m2rho(m):
return 1 / m * 1000
def rho2m(rho):
return 1 / rho / 1000
secax = axs.secondary_xaxis('top', functions=(m2rho, rho2m))
from matplotlib.ticker import ScalarFormatter, NullFormatter
for axis in [axs.xaxis, secax.xaxis]:
axis.set_major_formatter(ScalarFormatter())
axis.set_minor_formatter(NullFormatter())
axs.set_xticks([50, 100, 200])
secax.set_xticks([20,10,5])
plt.tight_layout()
plt.show()
Resulting in:
So essentially the second axis on top should display the ticks 20, 10 and 5 in non-scientific numbers, but it doesn't.
I'm plotting some function in matplotlib. But I want to change the usual x and y coordinates. For example I plot y=sin(x) in [-pi, pi]. But the x-axis shows 1, 2, 3,... in this way whereas I want x: -pi, 0, pi,... Is it possible?
My Code
import matplotlib as mpl
mpl.rc('text', usetex = True)
mpl.rc('font', family = 'serif')
import matplotlib.pyplot as plt
import numpy as np
plt.gca().set_aspect('equal', adjustable='box')
plt.style.use(['ggplot','dark_background'])
x = np.arange(-np.pi,np.pi,0.001)
y = np.sin(x)
plt.xlabel('$x$')
plt.ylabel('$y$')
plt.plot(x,y, label='$y=\sin x$')
plt.legend()
plt.show()
Output
How to change the marks on the axes coordinates? Thank you.
Yes, you can have custom tick marks on the axis, and set them equally spaced; for this you need to set the tick marks as a sequence, together with the values associated:
import matplotlib as mpl
mpl.rc('text', usetex = True)
mpl.rc('font', family = 'serif')
import matplotlib.pyplot as plt
import numpy as np
plt.gca().set_aspect('equal', adjustable='box')
plt.style.use(['ggplot','dark_background'])
x = np.arange(-np.pi,np.pi,0.001)
y = np.sin(x)
# the following two sequences contain the values and their assigned tick markers
xx = [-np.pi + idx*np.pi/4 for idx in range(10)]
xx_t = ['$-\\pi$', '$\\frac{-3\\pi}{4}$', '$\\frac{-\\pi}{2}$', '$\\frac{-\\pi}{4}$', '0',
'$\\frac{\\pi}{4}$', '$\\frac{\\pi}{2}$', '$\\frac{3\\pi}{4}$', '$\\pi$']
plt.xticks(xx, xx_t) # <-- the mapping happens here
plt.xlabel('$x$')
plt.ylabel('$y$')
plt.plot(x,y, label='$y=\sin x$')
plt.legend()
plt.show()
Here you can display up to whichever range of pi you want to. Just add the following lines to your code after plt.plot
xlabs = [r'%d$\pi$'%i if i!=0 else 0 for i in range(-2, 3, 1)]
xpos = np.linspace(-2*np.pi, 2*np.pi, 5)
plt.xticks(xpos, xlabs)
Output
Below is my code and plot:
import matplotlib
import matplotlib.mlab as mlab
import matplotlib.cm as cm
import matplotlib.pyplot as plt
import numpy as np
%matplotlib inline
delta = 0.00025
A=0
x = np.arange(0, 0.10, delta)
y = np.arange(0, 0.1, delta)
X, Y = np.meshgrid(x, y)
Z = A*(X**2+Y**2)+2*X*Y
manual_locations = [(0.1,0.1), (0.2,0.2), (0.3,0.3),
(0.015, 0.015), (0.00255, 0.0025), (0.00005,0.00005)]
line_widths = (1, 1, 1, 1, 1, 1)
plt.figure()
CS = plt.contour(X, Y, Z, 6, # add 6 contour lines
linewidths=line_widths, # line widths
colors = line_colours) # line colours
plt.clabel(CS, inline=1, # add labels
fontsize=10, # label font size
manual=manual_locations) # label locations
plt.title('Indifference Map') # title
plt.show()
It seems my manual_locations does nothing, python picks equally spaced contour lines automatically. While I want to investigate more details around 0. How can I see more curves/contour lines converging to (0,0)?
Thanks in advance.
The easiest way to explore parts of your data in more details is with levels. This sets what Z-values to examine, and in your question you phrase this as an (x,y) location to inspect, but it's a bit backwards from how contour works to specify the location points directly.
You could also do inspect the (0,0) region by changing the boundaries of the plot appropriately.
Below, I use log values for levels, but linear values work equally well, are far more common, and are easier to interpret. The log values just easily emphasis the part of the plot you're most interested in.
import matplotlib
import matplotlib.mlab as mlab
import matplotlib.cm as cm
import matplotlib.pyplot as plt
import numpy as np
#%matplotlib inline
delta = 0.00025
A=0
x = np.arange(0, 0.10, delta)
y = np.arange(0, 0.1, delta)
X, Y = np.meshgrid(x, y)
Z = A*(X**2+Y**2)+2*X*Y
manual_locations = [(0.1,0.1), (0.2,0.2), (0.3,0.3),
(0.015, 0.015), (0.00255, 0.0025), (0.00005,0.00005)]
line_widths = (1, 1, 1, 1, 1, 1)
plt.figure()
CS = plt.contour(X, Y, Z, 6, # add 6 contour lines
linewidths=line_widths,
#levels=np.linspace(0, .003, 20))
levels=np.logspace(-5, -2, 20))
plt.clabel(CS, inline=1, # add labels
fontsize=10,
fmt="%.5f")
plt.title('Indifference Map') # title
plt.show()
If you really need the contour at a specific location, you could put the (x,y) values for that location into your equation to calculate the z-value at that location, and then use this value as one of the values in the levels argument.