alignment of stacked subplots - python

EDIT:
I found myself an answer (see below) how to align the images within their subplots:
for ax in axes:
ax.set_anchor('W')
EDIT END
I have some data I plot with imshow. It's long in x direction, so I break it into multiple lines by plotting slices of the data in vertically stacked subplots. I am happy with the result but for the last subplot (not as wide as the others) which I want left aligned with the others.
The code below is tested with Python 2.7.1 and matplotlib 1.2.x.
#! /usr/bin/env python
import matplotlib.pyplot as plt
import numpy as np
x_slice = [0,3]
y_slices = [[0,10],[10,20],[20,30],[30,35]]
d = np.arange(35*3).reshape((35,3)).T
vmin = d.min()
vmax = d.max()
fig, axes = plt.subplots(len(y_slices), 1)
for i, s in enumerate(y_slices):
axes[i].imshow(
d[ x_slice[0]:x_slice[1], s[0]:s[1] ],
vmin=vmin, vmax=vmax,
aspect='equal',
interpolation='none'
)
plt.show()
results in
Given the tip by Zhenya I played around with axis.get/set_position. I tried to half the width but I don't understand the effect it has
for ax in axes:
print ax.get_position()
p3 = axes[3].get_position().get_points()
x0, y0 = p3[0]
x1, y1 = p3[1]
# [left, bottom, width, height]
axes[3].set_position([x0, y0, (x1-x0)/2, y1-y0])
get_position gives me the bbox of each subplot:
for ax in axes:
print ax.get_position()
Bbox(array([[ 0.125 , 0.72608696],
[ 0.9 , 0.9 ]]))
Bbox(array([[ 0.125 , 0.5173913 ],
[ 0.9 , 0.69130435]]))
Bbox(array([[ 0.125 , 0.30869565],
[ 0.9 , 0.4826087 ]]))
Bbox(array([[ 0.125 , 0.1 ],
[ 0.9 , 0.27391304]]))
so all the subplots have the exact same horizontal extent (0.125 to 0.9). Judging from the narrower 4th subplot the image inside the subplot is somehow centered.
Let's look at the AxesImage objects:
for ax in axes:
print ax.images[0]
AxesImage(80,348.522;496x83.4783)
AxesImage(80,248.348;496x83.4783)
AxesImage(80,148.174;496x83.4783)
AxesImage(80,48;496x83.4783)
again, the same horizontal extent for the 4th image too.
Next try AxesImage.get_extent():
for ax in axes:
print ax.images[0].get_extent()
# [left, right, bottom, top]
(-0.5, 9.5, 2.5, -0.5)
(-0.5, 9.5, 2.5, -0.5)
(-0.5, 9.5, 2.5, -0.5)
(-0.5, 4.5, 2.5, -0.5)
there is a difference (right) but the left value is the same for all so why is the 4th one centered then?
EDIT: They are all centered...

Axis.set_anchor works so far (I just hope I don't have to adjust too much manually now):
for ax in axes:
ax.set_anchor('W')

You can control the position of the subplot manually, like so:
for ax in axes:
print ax.get_position()
and
ax[3].set_position([0.1,0.2,0.3,0.4])
Alternatively, you may want to have a look at GridSpec

Often I found ax.set_position is very hard to be precise.
I would prefer to use plt.subplots_adjust(wspace=0.005) # adjust the width between the subplots to adjust the distance between the two horizontal subplots.
You can adjust the vertical distance as well.

Related

Tick label padding and tick label position on polar / radial chart - matplotlib

I am trying to rotate the radial tick labels on the attached plot.
Why does matplotlib not rotate them when I have the 'rotation' command specified?
I would then like to shift the labels in the radial direction. Is there an equivalent of the 'pad' command with the polar charts?
import numpy as np
import matplotlib.pyplot as plt
import math
Graph_title = "Radar Plot"
def radarplot():
ax = plt.subplot(111, polar=True)
# INPUT DATA
n_directions = 12
angles = [n / float(n_directions) * 2 * math.pi for n in range(n_directions)]
data = [3.0, 3.0, 3.0, 3.0, 2.0, 2.5, 2.5, 2.5, 2.75, 2.75, 3.0, 3.0]
# Add the last element of the list to the list. This is necessary or the line from 330 deg to 0 degree does not join up on the plot.
angles = np.append(angles, angles[:1])
data = np.append(data, data[:1])
ax.plot(angles, data, linewidth=2, linestyle='solid', color = 'red')
# Radial tick parameters
radial_ticks = [0.00, 0.50, 1.00, 1.50, 2.00, 2.50, 3.00]
ax.set_rlabel_position(45)
ax.set_rorigin(0)
plt.yticks(radial_ticks, color='black', size=8)
ax.set_yticklabels(radial_ticks, rotation = 45, zorder = 500)
# X Tick parameters
plt.xticks(angles, color='black', size=10, zorder = 5)
ax.tick_params(axis='x', which='major', pad=3)
ax.set_theta_zero_location("N") # Sets the labels initial position to 0 degrees
ax.set_theta_direction("clockwise") # Set the labels to rotate clockwise
plt.savefig(Graph_title +".png", figsize = [6.4, 5], dpi=1000)
plt.show()
plt.close()
radarplot()
Recently I wanted to achieve the same thing as you and here is the solution that I came up with.
Suppress the automatic r tick labels using the command ax.set_yticklabels([])
For each radial tick define a tick list, a position list, and an alignment list.
Using the text command write the values in the tick list at the radial locations specified by the position list with alignment specified by the alignment list.
Essentially the r ticks can be moved along the radial direction by changing the values in the position list.
Make sure that the text command is specified with transform=ax.transData option.
import numpy as np
import matplotlib.pyplot as plt
import math
Graph_title = "Radar Plot"
def radarplot():
ax = plt.subplot(111, polar=True)
# INPUT DATA
n_directions = 12
angles = [n / float(n_directions) * 2 * math.pi for n in range(n_directions)]
data = [3.0, 3.0, 3.0, 3.0, 2.0, 2.5, 2.5, 2.5, 2.75, 2.75, 3.0, 3.0]
# Add the last element of the list to the list. This is necessary or the line from 330 deg to 0 degree does not join up on the plot.
angles = np.append(angles, angles[:1])
data = np.append(data, data[:1])
ax.plot(angles, data, linewidth=2, linestyle='solid', color = 'red')
r_ticks = [0.00, 0.50, 1.00, 1.50, 2.00, 2.50, 3.00] #tick list
r_ticks_pos = [0.20, 0.65, 1.15, 1.65, 2.15, 2.65, 3.25] #radial position list (CHANGE THESE VALUES TO MOVE EACH TICK RADIALLY INDIVIDUALLY)
r_ticks_h_align = ['center','center','center','center','center','center','center'] #horizontal alignment list
r_ticks_v_align = ['center','center','center','center','center','center','center'] #vertical alignment list
r_label_angle = 45 #theta angle
# Radial tick parameters
ax.set_rlabel_position(r_label_angle)
ax.set_rorigin(0)
ax.set_yticklabels([])
ax.set_rticks(r_ticks)
ax.set_rlabel_position(r_label_angle)
#write the ticks using the text command
for rtick, rtick_pos, rtick_ha, rtick_va in zip(r_ticks, r_ticks_pos, r_ticks_h_align, r_ticks_v_align):
plt.text(np.radians(r_label_angle), rtick_pos, r'$'+str(rtick)+'$', ha=rtick_ha, va=rtick_va, transform=ax.transData, rotation=-45, fontsize=8)
# X Tick parameters
plt.xticks(angles, color='black', size=10, zorder = 5)
ax.tick_params(axis='x', which='major', pad=3)
ax.set_theta_zero_location("N") # Sets the labels initial position to 0 degrees
ax.set_theta_direction("clockwise") # Set the labels to rotate clockwise
plt.savefig(Graph_title +".png", figsize = [6.4, 5], dpi=1000)
plt.show()
plt.close()
radarplot()
And the result:
I meet the same problem, and I only solved the rotation problem. axis='x' failed to control the rotation, only axis='both' can work.
ax.tick_params(
axis='both',
labelrotation=-45.,
)
I am trying to solve the pad problem.

matplotlib subplots in a column with common horizontal colorbar

I have seen those nice solutions for making multiple subplots maintaining the aspect ratio "equal" of each subplot:
Matplotlib 2 Subplots, 1 Colorbar
https://stackoverflow.com/a/23953487/10659110
...but with a different geometry.
I wave 3 "square" plots (3 colormaps with the same x and y ranges) and I want to arrange them in a vertical column, with a single horizontal colorbar at the top or bottom.
Here an example:
#!/usr/bin/env python3
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
from pylab import meshgrid,cm,imshow,contour,clabel,colorbar,axis,title,show
Nlatt = 200
Lrange = 20
# Create grid values first.
xs = np.linspace(-Lrange, Lrange, Nlatt, endpoint=True)
ys = np.linspace(-Lrange, Lrange, Nlatt , endpoint=True)
X,Y = meshgrid(xs, ys) # grid of point
Zx = np.sin(Y-X)*np.sin(Y+X)
Zy = np.cos(Y)*np.sin(X)
ZZ = np.sqrt( Zx**2 + Zy**2 )
fig = plt.figure() #e.g. "2,3,4" means "2x3 grid, 4th subplot"
ax1 = fig.add_subplot(3,1,1, aspect = "equal")
ax2 = fig.add_subplot(3,1,2, aspect = "equal")
ax3 = fig.add_subplot(3,1,3, aspect = "equal")
im1 = ax1.pcolor( X , Y , Zx , cmap='bwr' ,vmin=-2.5, vmax=2.5)
im2 = ax2.pcolor( X , Y , Zy , cmap='bwr' ,vmin=-2.5, vmax=2.5)
im3 = ax3.pcolor( X , Y , ZZ , cmap='bwr' ,vmin=-2.5, vmax=2.5)
plt.tight_layout()
plt.show()
I want to add the common horizontal colorbar at the top (or at the bottom) and share the x-axis, so that the label "x" and the x-coordinate numbers appear only on the bottom of the third plot. in practice, the desired output is the one described in Matplotlib shared horizontal colorbar aligned with bottom axis in subplots , but it is not very clear to me how this solution works. Moreover, I also want a single label and numbers for the x-axis at the bottom. Thank you for every hint.

Having difficulties with normalizing an histogram in python

I'm working with an histogram plotted over and on the right of a scatter.
I've already tried the normal function inside plt.hist(norm=1, or density=True), and with that I obtain an histogram with y-axis size approximately 2, 5. I know that if I grow the bin size I can low that y-axis value, but the work I'm trying to replicate doesn't have a bin bigger than 0.2.
Code:
x,y=columns of a predetermined table
left, width = 0.1, 0.7
bottom, height = 0.1, 0.7
spacing = 0.05
rect_scatter = [left, bottom, width, height]
rect_histx = [left, bottom + height + spacing, width, 0.2]
rect_histy = [left + width + spacing, bottom, 0.2, height]
plt.figure(figsize=(9, 8))
ax_scatter = plt.axes(rect_scatter)
ax_scatter.tick_params(direction='in', top=True, right=True)
ax_histx = plt.axes(rect_histx)
ax_histx.tick_params(direction='in', labelbottom=True)
ax_histy = plt.axes(rect_histy)
ax_histy.tick_params(direction='in', labelleft=False)
ax_scatter.scatter(x, y, s=30, marker='*')
binwidth = 0.1
ax_scatter.set_xlim((-1, 0.7))
ax_scatter.set_ylim((-0.9, 0.9))
bins = np.arange(-10, 10 + binwidth, binwidth)
ax_histx.hist(x, bins=bins, normed=1, color='chartreuse')
ax_histy.hist(y, bins=bins, orientation='horizontal', normed=1, color='darkmagenta')
ax_histx.set_xlim(ax_scatter.get_xlim())
x_histy.set_ylim(ax_scatter.get_ylim())
P.S.: I've looked in other posts, and tried for a long time fixing it, but i'm really lost. Also, I'm new in programming and statistics. Therefore, if you can, use easy terms please.
I'm addressing a screenshot of the graphic I plotted with this function, and another with an example of the one I'm trying to achieve (the values are not supposed to be the same).
If you need anything else to help solve my problem, please, be free to ask. Thank you

How to make plots in normalized coordinates in python?

I am trying to make four sets of plots in a 2x2 or 1x4 grid. Each set then has three more panels, say, a scatter plot with histograms of the x- and y-axes on the sides.
Instead of setting the axes for all 12 plots, I'd like to divide my canvas into 4 parts, and then divide each one individually. For example,
def plot_subset():
# these coords are normalized to this subset of plots
pos_axScatter=[0.10, 0.10, 0.65, 0.65]
pos_axHistx = [0.10, 0.75, 0.65, 0.20]
pos_axHisty = [0.75, 0.10, 0.20, 0.20]
axScatter = plt.axes(pos_axScatter)
axHistx = plt.axes(pos_axHistx)
axHisty = plt.axes(pos_axHisty)
def main():
# need to divide the canvas to a 2x2 grid
plot_subset(1)
plot_subset(2)
plot_subset(3)
plot_subset(4)
plt.show()
I have tried GridSpec and subplots but cannot find a way to make plot_subset() work in the normalized space. Any help would be much appreciated!
You can use BboxTransformTo() to do this:
from matplotlib import transforms
fig = plt.figure(figsize=(16, 4))
fig.subplots_adjust(0.05, 0.05, 0.95, 0.95, 0.04, 0.04)
gs1 = plt.GridSpec(1, 4)
gs2 = plt.GridSpec(4, 4)
for i in range(4):
bbox = gs1[0, i].get_position(fig)
t = transforms.BboxTransformTo(bbox)
fig.add_axes(t.transform_bbox(gs2[:3, :3].get_position(fig)))
fig.add_axes(t.transform_bbox(gs2[3, :3].get_position(fig)))
fig.add_axes(t.transform_bbox(gs2[:3, 3].get_position(fig)))
the output:

Matplotlib: align origin of right axis with specific left axis value

When plotting several y axis in Matplotlib, is there a way to specify how to align the origin (and/or some ytick labels) of the right axis with a specific value of the left axis?
Here is my problem: I would like to plot two set of data as well as their difference (basically, I am trying to reproduce this kind of graph).
I can reproduce it, but I have to manually adjust the ylim of the right axis so that the origin is aligned with the value I want from the left axis.
I putted below an example of a simplified version of the code I use. As you can see, I have to manually adjust scale of the right axis to align the origin of the right axis as well as the square.
import numpy as np
import scipy as sp
import matplotlib.pyplot as plt
grp1 = np.array([1.202, 1.477, 1.223, 1.284, 1.701, 1.724, 1.099,
1.242, 1.099, 1.217, 1.291, 1.305, 1.333, 1.246])
grp2 = np.array([1.802, 2.399, 2.559, 2.286, 2.460, 2.511, 2.296,
1.975])
fig = plt.figure(figsize=(6, 6))
ax = fig.add_axes([0.17, 0.13, 0.6, 0.7])
# remove top and right spines and turn ticks off if no spine
ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none')
ax.spines['bottom'].set_color('none')
ax.xaxis.set_ticks_position('none')
ax.yaxis.set_ticks_position('left')
# postition of tick out
ax.tick_params(axis='both', direction='out', width=3, length=7,
labelsize=24, pad=8)
ax.spines['left'].set_linewidth(3)
# plot groups vs random numbers to create dot plot
ax.plot(np.random.normal(1, 0.05, grp2.size), grp2, 'ok', markersize=10)
ax.plot(np.random.normal(2, 0.05, grp1.size), grp1, 'ok', markersize=10)
ax.errorbar(1, np.mean(grp2), fmt='_r', markersize=50,
markeredgewidth=3)
ax.errorbar(2, np.mean(grp1), fmt='_r', markersize=50,
markeredgewidth=3)
ax.set_xlim((0.5, 3.5))
ax.set_ylim((0, 2.7))
# create right axis
ax2 = fig.add_axes(ax.get_position(), sharex=ax, frameon=False)
ax2.spines['left'].set_color('none')
ax2.spines['top'].set_color('none')
ax2.spines['bottom'].set_color('none')
ax2.xaxis.set_ticks_position('none')
ax2.yaxis.set_ticks_position('right')
# postition of tick out
ax2.tick_params(axis='both', direction='out', width=3, length=7,
labelsize=24, pad=8)
ax2.spines['right'].set_linewidth(3)
ax2.set_xticks([1, 2, 3])
ax2.set_xticklabels(('gr2', 'gr1', 'D'))
ax2.hlines(0, 0.5, 3.5, linestyle='dotted')
#ax2.hlines((np.mean(adult)-np.mean(nrvm)), 0, 3.5, linestyle='dotted')
ax2.plot(3, (np.mean(grp1)-np.mean(grp2)), 'sk', markersize=12)
# manual adjustment so the origin is aligned width left group2
ax2.set_ylim((-2.3, 0.42))
ax2.set_xlim((0.5, 3.5))
plt.show()
You can make a little function that calculates the alignment of ax2:
def align_yaxis(ax1, v1, ax2, v2):
"""adjust ax2 ylimit so that v2 in ax2 is aligned to v1 in ax1"""
_, y1 = ax1.transData.transform((0, v1))
_, y2 = ax2.transData.transform((0, v2))
inv = ax2.transData.inverted()
_, dy = inv.transform((0, 0)) - inv.transform((0, y1-y2))
miny, maxy = ax2.get_ylim()
ax2.set_ylim(miny+dy, maxy+dy)
by using align_yaxis(), you can align the axes quickly:
#...... your code
# adjustment so the origin is aligned width left group2
ax2.set_ylim((0, 2.7))
align_yaxis(ax, np.mean(grp2), ax2, 0)
plt.show()
The above answer is Okay, but sometimes cuts out data, it is more thoroughly answered in the second answer here,
Matplotlib axis with two scales shared origin
or with a quick hack
def align_yaxis(ax1, v1, ax2, v2, y2min, y2max):
"""adjust ax2 ylimit so that v2 in ax2 is aligned to v1 in ax1."""
"""where y2max is the maximum value in your secondary plot. I haven't
had a problem with minimum values being cut, so haven't set this. This
approach doesn't necessarily make for axis limits at nice near units,
but does optimist plot space"""
_, y1 = ax1.transData.transform((0, v1))
_, y2 = ax2.transData.transform((0, v2))
inv = ax2.transData.inverted()
_, dy = inv.transform((0, 0)) - inv.transform((0, y1-y2))
miny, maxy = ax2.get_ylim()
scale = 1
while scale*(maxy+dy) < y2max:
scale += 0.05
ax2.set_ylim(scale*(miny+dy), scale*(maxy+dy))

Categories