I have a time-longitude array which I am plotting using the matplotlib contourf function. My longitude values span from [-180, 180] and as such appear on the x-axis in this order.
I would like my x-axis to run from 0 degrees to 0 degrees, so my x-axis ticks would be (0, 60, 120, 180, -120, -60, 0). Is there an easy way to do this?
My current code is:
levels = np.arange(0, 5+0.5, 0.5)
lon_ticks = np.array([0, 60, 120, 180, -120, -60, 0])
for i in range(3):
fig = plt.figure(figsize = (15, 15))
ax = fig.add_subplot(1, 1, 1)
im = ax.contourf(lon,date_list,TRMM_lat_mean[:,:,i],
levels = levels, extend = 'both', cmap = 'gist_ncar')
cb = plt.colorbar(im)
plt.savefig("C:/Users/amcna/Desktop/fig{number}.png".format(number = i))
Which outputs:
!(https://imgur.com/epedcTu)
As you can see my longitude array spans from [-180, 180], however I wish it to be arranged in the order I specified above.
Since your data is cyclic, a representation through polar coordinates might work:
Example:
def f(x, y):
return np.sin(x) ** 10 + np.cos(10 + y * x) * np.cos(x)
x = np.radians([0, 60, 120, 180, -120, -60, 0])
y = np.arange(0, 5+0.5, 0.5)
X, Y = np.mesh
grid(x, y)
Z = f(X, Y)
#-- Plot... ------------------------------------------------
fig, ax = plt.subplots(subplot_kw=dict(projection='polar'))
ax.contourf(Y, X, Z)
plt.show()
If you don't want to do that, this thread might help you: Handling cyclic data with matplotlib contour/contourf
Related
I am trying to create a 3D barplot using matplotlib in python, and apply a colormap which is tied some data (4th dimension) which is not explicitly plotted. I think what makes this even more complicated is that I want this 4th dimension to be a range of values as opposed to a single value.
So far I have managed to create the 3D bar plot with a colormap tied to the z-dimension thanks primarily to this post how to plot gradient fill on the 3d bars in matplotlib. The code can be found below.
import numpy as np
import glob,os
from matplotlib import pyplot as plt
import matplotlib.colors as cl
import matplotlib.cm as cm
from mpl_toolkits.mplot3d import Axes3D
os.chdir('./')
# axis details for the bar plot
x = ['1', '2', '3', '4', '5'] # labels
x_tick_locks = np.arange(0.1, len(x) + 0.1, 1)
x_axis = np.arange(len(x))
y = ['A', 'B']
y_tick_locks = np.arange(-0.1, len(y) - 0.1, 1)
y_axis = np.arange(len(y))
x_axis, y_axis = np.meshgrid(x_axis, y_axis)
x_axis = x_axis.flatten()
y_axis = y_axis.flatten()
x_data_final = np.ones(len(x) * len(y)) * 0.5
y_data_final = np.ones(len(x) * len(y)) * 0.5
z_axis = np.zeros(len(x)*len(y))
z_data_final = [[30, 10, 15, 20, 25], [10, 15, 15, 28, 40]]
values_min = [[5, 1, 6, 8, 3], [2, 1, 3, 9, 4]]
values_max = [[20, 45, 11, 60, 30], [11, 28, 6, 30, 40]]
cmap_max = max(values_max)
cmap_min = min(values_min)
############################### FOR 3D SCALED GRADIENT BARS ###############################
def make_bar(ax, x0=0, y0=0, width = 0.5, height=1 , cmap="plasma",
norm=cl.Normalize(vmin=0, vmax=1), **kwargs ):
# Make data
u = np.linspace(0, 2*np.pi, 4+1)+np.pi/4.
v_ = np.linspace(np.pi/4., 3./4*np.pi, 100)
v = np.linspace(0, np.pi, len(v_)+2 )
v[0] = 0 ; v[-1] = np.pi; v[1:-1] = v_
#print(u)
x = np.outer(np.cos(u), np.sin(v))
y = np.outer(np.sin(u), np.sin(v))
z = np.outer(np.ones(np.size(u)), np.cos(v))
xthr = np.sin(np.pi/4.)**2 ; zthr = np.sin(np.pi/4.)
x[x > xthr] = xthr; x[x < -xthr] = -xthr
y[y > xthr] = xthr; y[y < -xthr] = -xthr
z[z > zthr] = zthr ; z[z < -zthr] = -zthr
x *= 1./xthr*width; y *= 1./xthr*width
z += zthr
z *= height/(2.*zthr)
#translate
x += x0; y += y0
#plot
ax.plot_surface(x, y, z, cmap=cmap, norm=norm, **kwargs)
def make_bars(ax, x, y, height, width=1):
widths = np.array(width)*np.ones_like(x)
x = np.array(x).flatten()
y = np.array(y).flatten()
h = np.array(height).flatten()
w = np.array(widths).flatten()
norm = cl.Normalize(vmin=0, vmax=h.max())
for i in range(len(x.flatten())):
make_bar(ax, x0=x[i], y0=y[i], width = w[i] , height=h[i], norm=norm)
############################### FOR 3D SCALED GRADIENT BARS ###############################
# Creating graph surface
fig = plt.figure(figsize=(9,6))
ax = fig.add_subplot(111, projection= Axes3D.name)
ax.azim = 50
ax.dist = 10
ax.elev = 30
ax.invert_xaxis()
ax.set_box_aspect((1, 0.5, 1))
ax.zaxis.labelpad=7
ax.text(0.9, 2.2, 0, 'Group', 'x')
ax.text(-2, 0.7, 0, 'Class', 'y')
ax.set_xticks(x_tick_locks)
ax.set_xticklabels(x, ha='left')
ax.tick_params(axis='x', which='major', pad=-2)
ax.set_yticks(y_tick_locks)
ax.set_yticklabels(y, ha='right', rotation=30)
ax.tick_params(axis='y', which='major', pad=-5)
ax.set_zlabel('Number')
make_bars(ax, x_axis, y_axis, z_data_final, width=0.2, )
fig.colorbar(plt.cm.ScalarMappable(cmap = 'plasma'), ax = ax, shrink=0.8)
#plt.tight_layout() # doesn't seem to work properly for 3d plots?
plt.show()
As I mentioned, I don't want the colormap to be tied to the z-axis but rather a 4th dimension, which is a range. In other words, I want the colours of the colormap to range from cmap_min to cmap_max (so min is 1 and max is 60), then for the bar plot with a z_data_final entry of 30 for example, its colours should correspond with the range of 5 to 20.
Some other posts seem to provide a solution for a single 4th dimensional value, i.e. (python) plot 3d surface with colormap as 4th dimension, function of x,y,z or How to make a 4d plot using Python with matplotlib however I wasn't able to find anything specific to bar plots with a range of values as your 4th dimensional data.
I would appreciate any guidance in this matter, thanks in advance.
This is the 3D bar plot with colormap tied to the z-dimension
I'm trying to render a 3D plot of a cost function. Given a dataset and two different parameters (theta0 and theta1), I'd like to render a bowl-like graph we all see in classic literature. My hypothesis function is just a simple h(x) = theta_0 + theta_1 * x. However, my cost function is being rendered as follows:
Is it ok to get this plot? In case it is, how can we plot such a "bowl"?
import matplotlib.pyplot as plt
import numpy as np
training_set = np.array([
[20, 400],
[30, 460],
[10, 300],
[50, 780],
[15, 350],
[60, 800],
[19, 360],
[31, 410],
[5, 50],
[46, 650],
[37, 400],
[39, 900]])
cost_factor = (1.0 / (len(training_set) * 2))
hypothesis = lambda theta0, theta1, x: theta0 + theta1 * x
cost = lambda theta0, theta1: cost_factor * sum(map(
lambda entry: (hypothesis(theta0, theta1, entry[0]) - entry[1]) ** 2, training_set))
theta1 = np.arange(0, 10, 1)
theta2 = np.arange(0, 10, 1)
X, Y = np.meshgrid(theta1, theta1)
Z = cost(X, Y)
ax = plt.axes(projection='3d')
ax.plot_surface(X, Y, Z, cmap='viridis', edgecolor='none')
ax.set_xlabel(r'$\theta_0$')
ax.set_ylabel(r'$\theta_1$')
ax.set_zlabel(r'$J(\theta_0, \theta_1)$')
ax.set_title('Cost function')
plt.show()
Side notes:
I have renamed theta1 to theta0 and theta2 to theta1 in your code in order to avoid confusion between the code and the labels of the plot
your code contains a typo: X, Y = np.meshgrid(theta1, theta1) should be X, Y = np.meshgrid(theta0, theta1)
You Z surface may have a point of absolute/relative minimum/maximum which is outside the domain you choose: 0 < theta0 < 10 and 0 < theta1 < 10. You can try to expand this interval in order to see if there actually is a stationary point:
theta0 = np.arange(-100, 100, 5)
theta1 = np.arange(-100, 100, 5)
So there is a minimum zone for -50 < theta1 < 50. It seems your 2D surface does not have a minimum along theta0 direction; however you can try to expand this domain as well:
theta0 = np.arange(-1000, 1000, 100)
theta1 = np.arange(-50, 50, 1)
So you can see that your Z surface does not have a minimium point, but a minimum zone which is not aligned with either theta0 nor theta1.
Since I do not know what theta0 and theta1actually represent, I may have assignd them values that have no sense: for example, if they are latitude and longitude respectively, then their domain should be -90 < theta0 < 90 and 0 < theta1 < 180. This depends on the physical meaning of theta0 and theta1.
However, you can always compute the gradient of the surface with np.gradient and plot them:
g1, g2 = np.gradient(Z)
fig = plt.figure()
ax1 = fig.add_subplot(1, 3, 1, projection = '3d')
ax2 = fig.add_subplot(1, 3, 2, projection = '3d')
ax3 = fig.add_subplot(1, 3, 3, projection = '3d')
ax1.plot_surface(X, Y, Z, cmap='viridis', edgecolor='none')
ax2.plot_surface(X, Y, g1, cmap='viridis', edgecolor='none')
ax3.plot_surface(X, Y, g2, cmap='viridis', edgecolor='none')
ax1.set_xlabel(r'$\theta_0$')
ax1.set_ylabel(r'$\theta_1$')
ax1.set_zlabel(r'$J(\theta_0, \theta_1)$')
ax1.set_title('Cost function')
ax2.set_xlabel(r'$\theta_0$')
ax2.set_ylabel(r'$\theta_1$')
ax3.set_xlabel(r'$\theta_0$')
ax3.set_ylabel(r'$\theta_1$')
plt.show()
You can see that the region where the gradient is null is a line, not a point.
If your Z surface would have a different expression, for example:
Z = np.exp(-X**2 - Y**2)
you would have:
In this case you can see that both gradient are null in the point (0, 0), where the surface has a maximum.
Here's what I came up with by plotting thick line segments.
The coloration is blue, with varying alpha, 0 < alpha < 1.
My workaround doens't work as I'd like because I don't have a legend (I want a legend that shows a gradient of the blue at varying alpha).
Additionally, I've found that matplotlib scales funny. There should be no overlap of the bars, but if I adjust the window size, the gap between the line segments will change.This is the same figure as the earlier one, just after I've resized the figure window with my mouse.
I'm not sure if there's a better way to go about accomplishing this, or if there's a different package I can use.
Here's the snippet of code that I'm using.
import matplotlib.pyplot as plt
x1 =[0, 19, 39, 46, 60, 79]
x2 = [19, 39, 46, 60, 79, 90]
alpha_list = [-0.8402, -0.6652, 0.0, -0.5106, -0.8074, 0.0]
plt.figure()
for idx,x in enumerate(x1):
plt.plot([x1[idx],x2[idx]],[0,0],color = 'blue',alpha=alpha_list[idx],linewidth =20)
plt.show()
I suppose alpha is just a workaround for using different shades of blue? In that case the Blues colormap can be used instead.
Several lines can be plotted using a LineCollection.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
x1 =[0, 19, 39, 46, 60, 79]
x2 = [19, 39, 46, 60, 79, 90]
alpha_list = [-0.8402, -0.6652, 0.0, -0.5106, -0.8074, 0.0]
verts = np.dstack((np.c_[x1, x2], np.zeros((len(x1), 2))))
fig, ax = plt.subplots()
lc = LineCollection(verts, linewidth=40, cmap="Blues_r", array=np.array(alpha_list))
ax.add_collection(lc)
ax.autoscale()
ax.set_ylim(-1,1)
fig.colorbar(lc)
plt.show()
I think a workaround would be to use plt.barh. Here is an example using normalized color maps. Each color gets converted to RGBA before it can be passed to plt.barh.
import matplotlib.pyplot as plt
from matplotlib import colors
import matplotlib.cm as cmx
x1 =[0, 19, 39, 46, 60, 79]
x2 = [19, 39, 46, 60, 79, 90]
values = range(len(x1))
jet = cm = plt.get_cmap('jet')
cNorm = colors.Normalize(vmin=0, vmax=values[-1])
scalarMap = cmx.ScalarMappable(norm=cNorm, cmap=jet)
fig, ax = plt.subplots()
for idx, x, y in zip(values,x1, x2):
colorVal = scalarMap.to_rgba(values[idx])
start = x
end = y
width=end-start
ax.barh(y = 0, width = width, left=start, height = 0.1, label = str(idx), color=colorVal)
ax.set_ylim(-.5,0.5)
ax.legend()
which returns:
If you really want to just change the alpha transparency of a single color, you would just have to input alpha_list[idx] for the last element to the RGBA tuple colorVal. For some reason, RGBA did not like negative alpha values, so notice I changed them all to positive
fig, ax = plt.subplots()
alpha_list = [0.8402, 0.6652, 0.01, 0.5106, 0.8074, 0.0]
for idx, x, y in zip(values,x1, x2):
colorVal = (0.0, 0.3, 1.0, alpha_list[idx])
start = x
end = y
width=end-start
ax.barh(y = 0, width = width, left=start, height = 0.1, label = str(idx), color=colorVal)
ax.set_ylim(-.5,0.5)
ax.legend()
I want to plot a Ramachandron plot. On this kind of graph, x goes from -180° to 180°, and so does y. I want a tick every 60 degrees. So here is the code I use:
import matplotlib.pyplot as plt
from matplotlib.colors import LogNorm
x = [-179, 179]
y = [-179, 179]
fig = plt.figure(1)
ax = plt.subplot(111)
ax.axis([-180, 180, -180, 180])
ax.set_xticks([-180, -120, -60, 0, 60, 120, 180])
ax.set_yticks([-180, -120, -60, 0, 60, 120, 180])
# 1 bim = 1 degree
# !!! Logarithmic normalization of the colors
plt.hist2d(x, y, bins=180, norm=LogNorm())
plt.colorbar()
plt.show()
On this working example, I'm plotting only two points. But the ticks -180 and 180 are not shown, for none of the axes:
If I change x and y to:
x = [-180, 180]
y = [-180, 180]
I get what I want:
Is there a way to achieve the second result without altering the data ?
You use hist2d, set axis ticks after plotting:
import matplotlib.pyplot as plt
from matplotlib.colors import LogNorm
x = [-179, 179]
y = [-179, 179]
fig = plt.figure(1)
ax = plt.subplot(111)
# 1 bim = 1 degree
# !!! Logarithmic normalization of the colors
plt.hist2d(x, y, bins=180, norm=LogNorm())
plt.colorbar()
ax.axis([-180, 180, -180, 180])
ax.set_xticks([-180, -120, -60, 0, 60, 120, 180])
ax.set_yticks([-180, -120, -60, 0, 60, 120, 180])
plt.show()
I'm trying to draw nice ticks (scalar not exponential) on a logarithmic y-axis in matplotlib. In general I want to include the first value (100in this example) an work from there. But in some cases I get different tickers like below. I have found no clue as how to manage this. Is there an uncomplicated way to force matplotlib to start with a specific value and automatically select sensible tickers thereafter (in this example 120, 110, 100, 90, 80, 70, 60, 50, 40, 30, 20 would be nice).
My code:
from matplotlib.ticker import ScalarFormatter, MaxNLocator
x = range(11)
y = [ 100., 91.3700879 , 91.01104689, 58.91189746,
46.99501432, 55.3816625 , 37.49715841, 26.55818469,
36.34538328, 37.7811044 , 47.45953131]
fig = plt.figure()
ax = fig.add_subplot(111)
ax.set_yscale('log')
ax.yaxis.set_major_locator(MaxNLocator(nbins=11, steps=[1,2,3,4,5,6,7,8,9,10]))
ax.yaxis.set_major_formatter(ScalarFormatter())
ax.plot(x,y)
Result:
You can use set_ylim():
ax.set_ylim(20, 120)
This could be one way to make the limits depend on the y-data instead of hard-wiring them:
ymax = round(max(y), -1) + 10
ymin = max(round(min(y), -1) - 10, 0)
ax.set_ylim(ymin, ymax)
You can force the tick locations with ax.set_yticks():
ymax = round(max(y), -1) + 20
ymin = max(round(min(y), -1) - 10, 0)
ax.set_ylim(ymin, ymax)
ax.set_yticks(range(int(ymin), int(ymax) + 1, 10))
ax.plot(x,y)
For:
y = [ 100. , 114.088362 , 91.14833261, 109.33399855, 73.34902925,
76.43091996, 56.84863363, 65.34297117, 78.99411287, 70.93280065,
55.03979689]
it produces this plot: