make axes the same length in matplotlib pyplot - python

I want to make square independent from the axis units. I know I can set the figure dimensions equal with figure(figsize=(10, 10)) for example, and I can set the axis scale ratio equal with set_aspect('equal'), but how can I force the actual axis length to be equal, e.g., make xaxis and yaxis each 10 inches long?
EDIT Example code
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
from matplotlib.ticker import MaxNLocator
import numpy as np
x1 = 1
y1 = [10., 1000.]
err1 = 0.00865
x2 = 2
y2 = [9., 900.]
err2 = 0.00658
len_xaxis,len_yaxis = 5.,5. #fix here your numbers
xspace, yspace = .9, .9 # change the size of the void border here.
x_fig,y_fig = len_xaxis / xspace, len_yaxis / yspace
for i in range(2):
plt.clf()
# fig = plt.figure(figsize=(6, 6))
fig = plt.figure(figsize=(x_fig,y_fig))
plt.subplots_adjust(left=1-xspace, right = xspace, top=yspace, bottom = 1-yspace)
gs = gridspec.GridSpec(3, 1)
gs.update(hspace=0., wspace=0.)
ax1 = plt.subplot(gs[0:2, 0])
ax1.errorbar(x1, y1[i], yerr=err1)
ax1.errorbar(x2, y2[i], yerr=err2)
ax1.invert_yaxis()
plt.setp(ax1.get_xticklabels(), visible=False) # Remove x-labels between the plots
plt.xlim(0, 3)
ax2 = plt.subplot(gs[2, 0], sharex=ax1)
nbins = len(ax1.get_yticklabels())
ax1.yaxis.set_major_locator(MaxNLocator(nbins=8, prune='both'))
nbins = len(ax2.get_yticklabels())
ax2.yaxis.set_major_locator(MaxNLocator(nbins=6, prune='both'))
plt.tight_layout()
plt.savefig('prune_%d.png' % i)
plt.close()

Play with the plt.subplots_adjust() function. Example for 5 inches instead of 10:
len_xaxis,len_yaxis = 5.,5. #fix here your numbers
xspace, yspace = .9, .9 # change the size of the void border here.
x_fig,y_fig = len_xaxis / xspace, len_yaxis / yspace
figure(figsize=(x_fig,y_fig))
plt.subplots_adjust(left=1-xspace, right = xspace, top=yspace, bottom = 1-yspace)
plot([1,2,3],[-1,3,5])

Related

Display matplotlib legend element as 2D line of colormap

I wish to modify the 2D line in my legend to plot as line segments (or another method like patches) that will display the range of my colormap (here viridis_r) instead of a singular color. While the third variable (radius) is included in the colorbar, having it displayed in the legend as well will be informative when I add more complications to the plot. Thanks!
fig, ax = plt.subplots()
radii = [1,2,3,4,5]
angle = np.linspace(0, 2 * np.pi, 150)
cmap = plt.get_cmap('viridis_r')
norm = plt.Normalize(radii[0], radii[-1])
m = plt.cm.ScalarMappable(cmap=cmap)
m.set_array(radii)
for radius in radii:
x = radius * np.cos(angle)
y = radius * np.sin(angle)
ax.plot(x, y, color=cmap(norm(radius)))
radius_2Dline = plt.Line2D((0, 1), (0, 0), color='k', linewidth=2)
ax.legend([radius_2Dline],['Radius'], loc='best')
ax.set_aspect( 1 )
fig.colorbar(m).set_label('Radius', size=15)
plt.show()
The following approach uses the "tuple legend handler". That handler puts a list of legend handles (in this case the circles drawn via ax.plot). Setting ndivide=None will draw one short line for each element in the list. The padding can be set to 0 to avoid gaps between these short lines. The default handlelength might be too small to properly see these special handles; therefore, the example code below increases it a bit.
import matplotlib.pyplot as plt
from matplotlib.legend_handler import HandlerTuple
import numpy as np
fig, ax = plt.subplots()
radii = [1, 2, 3, 4, 5]
angle = np.linspace(0, 2 * np.pi, 150)
cmap = plt.get_cmap('viridis_r')
norm = plt.Normalize(radii[0], radii[-1])
lines = [] # list of lines to be used for the legend
for radius in radii:
x = radius * np.cos(angle)
y = radius * np.sin(angle)
line, = ax.plot(x, y, color=cmap(norm(radius)))
lines.append(line)
ax.legend(handles=[tuple(lines)], labels=['Radius'],
handlelength=3, handler_map={tuple: HandlerTuple(ndivide=None, pad=0)})
ax.set_aspect('equal')
plt.tight_layout()
plt.show()
I am not sure if this is your goal but here is a stab at it. Following this answer, you can make a 'fake' legend with a colormap.
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1.inset_locator import inset_axes
fig, ax = plt.subplots()
radii = [1, 2, 3, 4, 5]
angle = np.linspace(0, 2 * np.pi, 150)
cmap = plt.get_cmap('viridis_r')
norm = plt.Normalize(radii[0], radii[-1])
m = plt.cm.ScalarMappable(cmap=cmap)
m.set_array(radii)
for radius in radii:
x = radius * np.cos(angle)
y = radius * np.sin(angle)
ax.plot(x, y, color=cmap(norm(radius)))
# Set box that will act as a 'fake' legend, 25% width of the
# x-axis, 15% of y-axis
cbbox = inset_axes(ax, width="25%", height="15%", loc=2)
cbbox.tick_params(
axis = 'both',
left = False,
top = False,
right = False,
bottom = False,
labelleft = False,
labeltop = False,
labelright = False,
labelbottom = False
)
# Semi-transparent like the usual ax.legend()
cbbox.set_facecolor([1, 1, 1, 0.7])
# Colorbar inside the fake legend box, occupying 85% of the
# box width and %5 box height
cbaxes = inset_axes(cbbox, width="85%", height="5%", loc=2)
cbar = fig.colorbar(m, cax=cbaxes, orientation='horizontal',
ticks=[1, 3, 5])
cbar.set_label('Radius', size=9)
cbar.ax.tick_params(labelsize=9)
ax.set_aspect(1)
plt.show()
I was unsuccessful in creating an actual ax.legend() from a LineCollection or a multicolored line - it only plotted one color - so my solution was this 'fake' legend approach. Hope this helps, cheers.

bar x-tick not as same as the image

Im not sure if i use the wrong data or if there is and edit i need to do and not seeing it. It would be nice if someone could take a look at the code. The problem here is that yerr at the first bar is at x=0 and in the image the yerr is somewhere around 2.5
Does someone know what i did wrong or forgot to edit?
the end result should be:
my code:
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(1)
y_raw = np.random.randn(1000).cumsum() + 15
x_raw = np.linspace(0, 24, y_raw.size)
x_pos = x_raw.reshape(-1, 100).min(axis=1)
y_avg = y_raw.reshape(-1, 100).mean(axis=1)
y_err = y_raw.reshape(-1, 100).ptp(axis=1)
bar_width = x_pos[1] - x_pos[0]
x_pred = np.linspace(0, 30)
y_max_pred = y_avg[0] + y_err[0] + 2.3 * x_pred
y_min_pred = y_avg[0] - y_err[0] + 1.2 * x_pred
barcolor, linecolor, fillcolor = 'wheat', 'salmon', 'lightblue'
fig, axes = fig, ax = plt.subplots()
axes.set_title(label="Future Projection of Attitudes", fontsize=15)
plt.xlabel('Minutes since class began', fontsize=12)
plt.ylabel('Snarkiness (snark units)', fontsize=12)
fig.set_size_inches(8, 6, forward=True)
axes.fill_between(x_pred, y_min_pred, y_max_pred ,color='lightblue')
axes.plot(x_raw, y_raw, color='salmon')
vert_bars = axes.bar(x_pos, y_avg, yerr=y_err, color='wheat', width = bar_width, edgecolor='grey',error_kw=dict(lw=1, capsize=5, capthick=1, ecolor='gray'))
axes.set(xlim=[0, 30], ylim=[0,100])
plt.show()
yerr is meant to be the difference between the mean and the min/max. Now you're using the full difference between max and min. You might divide it by 2 to get a better approximation. To obtain the exact values, you could calculate them explicitly (see code example).
Further, by default, the bars are center aligned vs their x-position. You can use align='edge' to left-align them (as x_pos is calculated as the minimum of the range the bar represents). You could also set clip_on=False in the err_kw to make sure the error bars are never clipped by the axes.
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(1)
y_raw = np.random.randn(1000).cumsum() + 15
x_raw = np.linspace(0, 24, y_raw.size)
x_pos = x_raw.reshape(-1, 100).min(axis=1)
y_avg = y_raw.reshape(-1, 100).mean(axis=1)
y_min = y_raw.reshape(-1, 100).min(axis=1)
y_max = y_raw.reshape(-1, 100).max(axis=1)
bar_width = x_pos[1] - x_pos[0]
x_pred = np.linspace(0, 30)
y_max_pred = y_avg[0] + y_err[0] + 2.3 * x_pred
y_min_pred = y_avg[0] - y_err[0] + 1.2 * x_pred
barcolor, linecolor, fillcolor = 'wheat', 'salmon', 'lightblue'
fig, ax = plt.subplots(figsize=(8, 6))
ax.set_title(label="Future Projection of Attitudes", fontsize=15)
ax.set_xlabel('Minutes since class began', fontsize=12)
ax.set_ylabel('Snarkiness (snark units)', fontsize=12)
ax.fill_between(x_pred, y_min_pred, y_max_pred, color='lightblue')
ax.plot(x_raw, y_raw, color='salmon')
vert_bars = ax.bar(x_pos, y_avg, yerr=(y_avg - y_min, y_max - y_avg),
color='wheat', width=bar_width, edgecolor='grey', align='edge',
error_kw=dict(lw=1, capsize=5, capthick=1, ecolor='grey', clip_on=False))
ax.set(xlim=[0, 30], ylim=[0, 100])
plt.tight_layout()
plt.show()

How to give same space for y axis tick labels in Matplotlib for different figures?

I need to plot figures using the same function.
Yaxis tick labels can be 2 digit and sometimes 3 digit integer numbers.
Although I format the y axis tick labels as 3 digit integers, spacing before the left spine slightly changes.
How to give same space for the y axis tick labels so that the left spine of the axis starts at the same location for different figures?
Here is the sample code to replicate these figures:
from matplotlib import pyplot as plt
from matplotlib.ticker import FormatStrFormatter
from matplotlib.ticker import MaxNLocator
import numpy as np
import os
def plot_figure(start, end, figure_name):
fwidth = 15
fheight = 7
fig = plt.figure(figsize=(fwidth, fheight), facecolor=None)
plt.style.use('ggplot')
# define margins
left_margin = 0.95 / fwidth
right_margin = 0.2 / fwidth
bottom_margin = 0.5 / fheight
top_margin = 0.25 / fheight
# create axes
x = left_margin # horiz. position of bottom-left corner
y = bottom_margin # vert. position of bottom-left corner
w = 1 - (left_margin + right_margin) # width of axes
h = 1 - (bottom_margin + top_margin) # height of axes
ax = fig.add_axes([x, y, w, h])
ax.set_facecolor('white')
# This code puts the edge line
for edge_i in ['left', 'bottom','right', 'top']:
ax.spines[edge_i].set_edgecolor("black")
ax.spines[edge_i].set_linewidth(3)
plus_minus = 50
x = np.arange(-plus_minus, plus_minus + 1, 1)
signal_array = np.random.randint(start, end + 1, size = 2*plus_minus+1)
plt.plot(x, signal_array, color='b', label='Signal', linewidth=2, zorder=10)
# This code puts the tick marks
plt.tick_params(axis='both', which='major', labelsize=50, width=3, length=10)
plt.tick_params(axis='both', which='minor', labelsize=50, width=3, length=10)
# This code provides the x and y tick marks and labels
plt.xticks(np.arange(-plus_minus/2, plus_minus/2+1, step=plus_minus/2), fontsize=50)
plt.xlim((-plus_minus, plus_minus))
ax.yaxis.set_major_formatter(FormatStrFormatter('%3d'))
ax.yaxis.set_major_locator(MaxNLocator(integer=True, min_n_ticks=3, nbins=2))
ax.yaxis.set_major_locator(MaxNLocator(3))
figure_file = os.path.join('/Users','burcakotlu','Desktop','test2.png')
fig.savefig(figure_file, dpi=100, bbox_inches="tight")
plt.close(fig)
plot_figure(20, 50, 'test1')
plot_figure(100, 130, 'test2')
Maybe you should consider to plot within the same fig and add sharex=True option with something like this:
from matplotlib import pyplot as plt
from matplotlib.ticker import FormatStrFormatter
from matplotlib.ticker import MaxNLocator
import numpy as np
import os
fwidth = 15
fheight = 7
fig, axs = plt.subplots(2, 1, figsize=(fwidth, fheight), sharex=True, dpi=100)
plt.style.use('ggplot')
# This code provides the x and y tick marks and labels
plus_minus = 50
plt.xticks(np.arange(-plus_minus/2, plus_minus/2+1, step=plus_minus/2), fontsize=50)
plt.xlim((-plus_minus, plus_minus))
for i, se in enumerate([(20, 50), (100, 130)]):
ax = axs[i]
ax.set_facecolor('white')
# This code puts the edge line
for edge_i in ['left', 'bottom','right', 'top']:
ax.spines[edge_i].set_edgecolor("black")
ax.spines[edge_i].set_linewidth(3)
x = np.arange(-plus_minus, plus_minus + 1, 1)
signal_array = np.random.randint(se[0], se[1] + 1, size = 2*plus_minus+1)
ax.plot(x, signal_array, color='b', label='Signal', linewidth=2, zorder=10)
# This code puts the tick marks
ax.tick_params(axis='both', which='major', labelsize=50, width=3, length=10)
ax.tick_params(axis='both', which='minor', labelsize=50, width=3, length=10)
ax.yaxis.set_major_formatter(FormatStrFormatter('%3d'))
ax.yaxis.set_major_locator(MaxNLocator(integer=True, min_n_ticks=3, nbins=2))
ax.yaxis.set_major_locator(MaxNLocator(3))
plt.show()
If you know the range of your data on the y-axis, you could use ylim to ensure that the yticks are spaced evenly across plots.
See code here:
from matplotlib import pyplot as plt
from matplotlib.ticker import FormatStrFormatter
from matplotlib.ticker import MaxNLocator
import numpy as np
import os
def plot_figure(start, end, figure_name):
fwidth = 15
fheight = 7
fig = plt.figure(figsize=(fwidth, fheight), facecolor=None)
plt.style.use('ggplot')
# define margins
left_margin = 0.95 / fwidth
right_margin = 0.2 / fwidth
bottom_margin = 0.5 / fheight
top_margin = 0.25 / fheight
# create axes
x = left_margin # horiz. position of bottom-left corner
y = bottom_margin # vert. position of bottom-left corner
w = 1 - (left_margin + right_margin) # width of axes
h = 1 - (bottom_margin + top_margin) # height of axes
ax = fig.add_axes([x, y, w, h])
ax.set_facecolor('white')
# This code puts the edge line
for edge_i in ['left', 'bottom','right', 'top']:
ax.spines[edge_i].set_edgecolor("black")
ax.spines[edge_i].set_linewidth(3)
plus_minus = 50
x = np.arange(-plus_minus, plus_minus + 1, 1)
signal_array = np.random.randint(start, end + 1, size = 2*plus_minus+1)
plt.plot(x, signal_array, color='b', label='Signal', linewidth=2, zorder=10)
# This code puts the tick marks
plt.tick_params(axis='both', which='major', labelsize=50, width=3, length=10)
plt.tick_params(axis='both', which='minor', labelsize=50, width=3, length=10)
# This code provides the x and y tick marks and labels
plt.xticks(np.arange(-plus_minus/2, plus_minus/2+1, step=plus_minus/2), fontsize=50)
plt.xlim((-plus_minus, plus_minus))
plt.ylim([0,160])
ax.yaxis.set_major_formatter(FormatStrFormatter('%3d'))
ax.yaxis.set_major_locator(MaxNLocator(integer=True, min_n_ticks=3, nbins=2))
ax.yaxis.set_major_locator(MaxNLocator(3))
figure_file = os.path.join('/Users','burcakotlu','Desktop','test2.png')
plt.show()
plot_figure(20, 50, 'test1')
plot_figure(100, 130, 'test2')
And the output gives:

Matplotlib: align bar plots with imgshow once on x axis and once on y axis

I'm trying to make a plot showing the sum of pixel intensities along the x and y axis.
Currently I have the following code:
def example():
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec
## <not under my control>
np.random.seed(1455)
width = 5
height = 8
sample = np.random.rand(height,width)
x_hist = np.sum(sample, axis=0)
y_hist = np.sum(sample, axis=1)
## </not under my control>
fig = plt.figure()
gs = fig.add_gridspec(2, 2)
ax_joint = fig.add_subplot(gs[1, 0])
ax_marg_x = fig.add_subplot(gs[0, 0],sharex=ax_joint)
ax_marg_y = fig.add_subplot(gs[1, 1],sharey=ax_joint)
ax_joint.imshow(sample, cmap="Reds")
ax_marg_x.bar(range(width),x_hist)
ax_marg_y.barh(range(height),y_hist)
plt.show()
Which yields the following:
However the x axis of the upper bar plot and image have the same limits but aren't scaled or aligned
Also there is a large gap between the image and the right bar plot.
My desired result would be something along the lines of:
As mentioned in the comments, correct the aspect ratio to automatic and set the size of the figure to portrait. In addition, make the spacing between each narrower.
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec
np.random.seed(1455)
width = 5
height = 8
sample = np.random.rand(height,width)
x_hist = np.sum(sample, axis=0)
y_hist = np.sum(sample, axis=1)
fig = plt.figure(figsize=(6,8))
gs = fig.add_gridspec(2, 2, hspace=0.1, wspace=0.1)
ax_joint = fig.add_subplot(gs[1, 0])
ax_marg_x = fig.add_subplot(gs[0, 0],sharex=ax_joint)
ax_marg_y = fig.add_subplot(gs[1, 1],sharey=ax_joint)
ax_joint.imshow(sample, cmap="Reds", aspect='auto')
ax_marg_x.bar(range(width),x_hist)
ax_marg_y.barh(range(height),y_hist)
plt.show()

Loop to create subplot /Python

i have a little problem to create a subplot loop.
The following code show my result for one plot.... So it starts with a dayloop than with a hour loop (8 timesteps).
If i run the code i get a nice QUiver plot with the colorbar.
for dd in range(1,15):
day=str(dd)
readfile=fns[files_indizes[dd]]
if dd < 10:
nc_u_comp = NetCDFFile(ROOT+u_comp1+'0'+day+comp)
nc_v_comp = NetCDFFile(ROOT+v_comp1+'0'+day+comp)
else:
nc_u_comp = NetCDFFile(ROOT+u_comp1+day+comp)
nc_v_comp = NetCDFFile(ROOT+v_comp1+day+comp)
time = nc_u_comp.variables['time'][:]
index=readfile.find(comp)
index=index+len(comp)
date=readfile[index-14:index-6]
plt.clf()
for tt in range(0,len(time)):
if tt < 10:
h =str(0)+str(tt)
else:
h=str(tt)
varU=nc_u_comp.variables['u10'][tt,:,:]
varV=nc_v_comp.variables['v10'][tt,:,:]
lat = nc_u_comp.variables['latitude'][:]
lon = nc_u_comp.variables['longitude'][:]
plt.rcParams["figure.figsize"] = [10,10]
#plane projection of the world
#map with box size (defintion on the top)
box = sgeom.box(minx=llcrnrlon, maxx=urcrnrlon, miny=llcrnrlat, maxy=urcrnrlat)
x0, y0, x1, y1 = box.bounds
#Map plot. The middel of the map is central_longitude
#proj = ccrs.PlateCarree(central_longitude=0)
proj=ccrs.PlateCarree()
#Change middelpoint of the map
box_proj = ccrs.PlateCarree(central_longitude=0)
ax2 = plt.axes(projection=proj)
ax2.set_extent([x0, x1, y0, y1], box_proj)
ax2.add_feature(cartopy.feature.BORDERS, linestyle='-', alpha=.5)
ax2.coastlines(resolution='50m')
#Definition of the scale_bar
gl = ax2.gridlines(ccrs.PlateCarree(), \
linestyle='--', alpha=1, linewidth=0.5, draw_labels=True)
gl.xlabels_top = False
gl.ylabels_right = False
gl.xformatter = LONGITUDE_FORMATTER
gl.yformatter = LATITUDE_FORMATTER
magnitude = (varU ** 2 + varV ** 2) ** 0.5
strm =plt.streamplot(lon , lat , varU, varV, linewidth=2, density=2, color=magnitude)
cbar= plt.colorbar()
cbar.set_label('$m/s$')
name='Wind in 10 m '+ date + h+' UTC'
ax2.set_aspect('auto')
plt.title(name, y=1)
Now i want to create an 2x4 Subplot array with a colorbar allocate to the complete Subplot array.
I find some infromation in the internet, but it doesn't run with my code. Maybe someone can help me?
This shows how to plot an array of simple Cartopy maps in 4 rows 2 columns. Also shows how to plot a colorbar to accompany the maps array. Hope it helps.
import numpy as np
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
import matplotlib as mpl
# create figure with figsize big enough to accomodate all maps, labels, etc.
fig = plt.figure(figsize=(8, 10), tight_layout=False)
# define plot array's arrangement
columns = 2
rows = 4
# set projection to use
projex = ccrs.PlateCarree()
# set the colormap and norm for
# the colorbar to use
cmap1 = mpl.cm.magma
norm1 = mpl.colors.Normalize(vmin=0, vmax=100)
def plotmymap(axs):
# your plot specs of each map should replace this
img = np.random.randint(100, size=(15, 30)) # 2d array of random values (1-100)
# render image on current axis
plims = plt.imshow(img, extent=[-180,180,-90,90], alpha=0.5, cmap=cmap1, norm=norm1)
axs.set_global()
axs.coastlines()
# add title to the map
axs.set_title("Map_"+str(i))
return plims # for use by colorbar
for i in range(1, columns*rows +1):
# add a subplot into the array of plots
ax = fig.add_subplot(rows, columns, i, projection=projex)
plims = plotmymap(ax) # a simple maps is created on subplot
# add a subplot for vertical colorbar
bottom, top = 0.1, 0.9
left, right = 0.1, 0.8
fig.subplots_adjust(top=top, bottom=bottom, left=left, right=right, hspace=0.15, wspace=0.25)
cbar_ax = fig.add_axes([0.85, bottom, 0.05, top-bottom])
fig.colorbar(plims, cax=cbar_ax) # plot colorbar
plt.show() # this plot all the maps
The resulting plots:

Categories