Move and align subplots to match a specific layout - python

I have an nxn matrix that I want to plot, and I also want to plot the sum of rows and cols.
So I have this:
data = np.random.randn(5, 5)
fig, axes = plt.subplots(2, 2)
axes[0, 0].imshow(data)
axes[0, 1].imshow(data.sum(axis=1).reshape(-1, 1))
axes[1, 0].imshow(data.sum(axis=0).reshape(1, -1))
How can I align the row and column to the main image and put them closer to it?
I would also like to get rid of the empty axis in the bottom right.

You can try:
data = np.random.randn(5, 5)
fig, axes = plt.subplots(2, 2,
gridspec_kw={'height_ratios': [5, 1],
'width_ratios': [5, 1]
}
)
axes[0, 0].imshow(data)
axes[0, 1].imshow(data.sum(axis=1).reshape(-1, 1))
axes[1, 0].imshow(data.sum(axis=0).reshape(1, -1))
axes[1, 1].set_visible(False)
plt.tight_layout()
Output:

Related

How can I add text to the same position in multiple matplotlib plots with different axis scales?

I have ~20 plots with different axes, ranging from scales of 0-1 to 0-300. I want to use plt.text(x,y) to add text to the top left corner in my automated plotting function, but the changing axis size does not allow for this to be automated and completely consistent.
Here are two example plots:
import matplotlib.pyplot as plt
plt.plot([1, 2, 3, 4])
plt.ylabel('some numbers')
plt.show()
#Plot 2
plt.plot([2, 4, 6, 8])
plt.ylabel('some numbers')
plt.show()
I want to use something like plt.text(x, y, 'text', fontsize=8) in both plots, but without specifying the x and y for each plot by hand, instead just saying that the text should go in the top left. Is this possible?
Have you tried ax.text with transform=ax.transAxes?
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
ax.plot([1, 2, 3, 4])
ax.set_ylabel('XXX')
ax.text(0.05, 0.95, 'text', transform=ax.transAxes, fontsize=8, va='top', ha='left')
plt.show()
Explanation:
The ax.text takes first the x and y coordinates of the text, then the transform argument which specifies the coordinate system to use, and the va and ha arguments which specify the vertical and horizontal alignment of the text.
Use ax.annotate with axes fraction coordinates:
fig, axs = plt.subplots(1, 2)
axs[0].plot([0, 0.8, 1, 0.5])
axs[1].plot([10, 300, 200])
for ax in axs:
ax.annotate('text', (0.05, 0.9), xycoords='axes fraction')
# ------------------------
Here (0.05, 0.9) refers to 5% and 90% of the axes lengths, regardless of the data:

Fixed ylabel space (aligned y-axis) across multiple figures

I'm using code much like:
import matplotlib.pyplot as plt
labels = ['AAA', 'BBBBBBBBBBBBB', 'CCCCCC', 'DDDDDDDDDD']
values = [0, 2, 2, 5]
fig, ax = plt.subplots(figsize=(8, 0.07 + 0.25 * len(values)))
bars = ax.barh(labels, values, color=colors)
to generate horizontal bar plots as separate figures, one after another:
How can I make the left spines (i.e. the black bars) align when the width of labels varies between plots? (Aside from just aligning the rendered images to the right.)
I think the left margin/padding/space should be fixed, or the bar width should be fixed, but I can't quite figure how to do it.
In these cases, I just add empty axes at the left edge of each figure. I'm sure there are more sophisticated ways, but I find this to be simplest:
fig1 with blank axes at left location
fig1, ax = plt.subplots(figsize=(8, 1))
ax.barh(['AAA', 'BBBBBBBBBBBBB', 'CCCCCC', 'DDDDDDDDDD'], [0, 2, 2, 5])
# add empty axes at `left` location (unit: fraction of figure width)
left = -0.05 # requires manual adjustment
fig1.add_axes([left, 0, 0, 0.01]).axis('off')
plt.show()
fig2 with blank axes at same left location as fig1
fig2, ax = plt.subplots(figsize=(8, 1))
ax.barh(['AAaaaA', 'BBBB', 'CCCCCC', 'DDDDD'], [2, 8, 7, 1])
# add empty axes at same `left` location as fig1
fig2.add_axes([left, 0, 0, 0.01]).axis('off')
plt.show()
Output of fig1 and fig2:
A similar approach would be to annotate a space character at the left of each figure:
ax.annotate(' ', (left, 0), xycoords='axes fraction', annotation_clip=False)

How to move exponent label with spine in matplotlib twin_x plot?

I am using twin_x() to create a plot with shared twin axes. To create a MWE, I used an example matplotlib provides in their documentation also provided below in the code block.
If I make the y values of the twin axes that has the offset large, then the tick labels will have an exponential factor as desired. However, this value does not move with the spine.
How can I get the exponent factor label to move with the spline?
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots()
fig.subplots_adjust(right=0.75)
twin1 = ax.twinx()
twin2 = ax.twinx()
# Offset the right spine of twin2. The ticks and label have already been
# placed on the right by twinx above.
twin2.spines.right.set_position(("axes", 1.2))
y = 1e6*np.linspace(1,2,3)
p1, = ax.plot([0, 1, 2], [0, 1, 2], "b-", label="Density")
p2, = twin1.plot([0, 1, 2], [0, 3, 2], "r-", label="Temperature")
p3, = twin2.plot([0, 1, 2], y, "g-", label="Velocity")
ax.set_xlabel("Distance")
ax.set_ylabel("Density")
twin1.set_ylabel("Temperature")
twin2.set_ylabel("Velocity")
ax.yaxis.label.set_color(p1.get_color())
twin1.yaxis.label.set_color(p2.get_color())
twin2.yaxis.label.set_color(p3.get_color())
tkw = dict(size=4, width=1.5)
ax.tick_params(axis='y', colors=p1.get_color(), **tkw)
twin1.tick_params(axis='y', colors=p2.get_color(), **tkw)
twin2.tick_params(axis='y', colors=p3.get_color(), **tkw)
ax.tick_params(axis='x', **tkw)
ax.legend(handles=[p1, p2, p3])
plt.show()
I have included a picture to help demonstrate. I want to move the 1e6 to be near the axis spine for velocity since it represents the velocity value.
You can do this by getting the text of the second axis and setting it to the position of the second axis. .set_position(1.2,1.1) can be set arbitrarily, you can adjust it yourself. This answer is based on this answer.
ax.legend(handles=[p1, p2, p3])
# update
twin2.get_yaxis().get_offset_text().set_position((1.2,1.1))
plt.show()

How to group subplots by adjusting spaces in between

I have a subplots that look as follows:
import matplotlib.pyplot as plt
x = [1, 2, 3]
y = [4, 5, 6]
fig_shape, axs_shape = plt.subplots(2, 6, figsize=(6, 6))
for i in range(2):
for j in range(6):
axs_shape[i, j].xaxis.set_major_locator(plt.NullLocator())
axs_shape[i, j].yaxis.set_major_locator(plt.NullLocator())
for i in range(6):
axs_shape[int(i / 3), 2 * (i % 3)].plot(x, y)
axs_shape[int(i / 3), 2 * (i % 3) + 1].plot(x, y)
What I want is, that the subplots are grouped in pairs of two. That means, in each row, I want plot 0 and 1 to be right next to each other (no space in between). Then a small space and followed by plot 2 and 3 right next to each other. Then a space and plot 4 and 5 right next to each other. I read, that you can adjust sizes with .tight_layout() and subplots_adjust, but I couldn't figure out a solution for this particular behavior. Thanks a lot for your help!
You can use nested gridspecs:
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
x = [1, 2, 3]
y = [4, 5, 6]
fig = plt.figure(figsize=(12, 5))
outer = gridspec.GridSpec(nrows=2, ncols=3)
axs = []
for row in range(2):
for col in range(3):
inner = gridspec.GridSpecFromSubplotSpec(nrows=1, ncols=2, subplot_spec=outer[row, col], wspace=0)
axs += [plt.subplot(cell) for cell in inner]
for ax in axs:
ax.plot(x, y)
ax.set_yticks([])
ax.set_xticks([])
plt.tight_layout()
plt.show()
PS: As mentioned in the other answer, matplotlib has implemented subfigures as a new feature. If I understand correctly, the above example would be more or less as follows:
import matplotlib.pyplot as plt
x = [1, 2, 3]
y = [4, 5, 6]
fig = plt.figure(figsize=(12, 5), constrained_layout=True)
subfigs = fig.subfigures(nrows=2, ncols=3, wspace=0.07)
axs = [subfig.subplots(nrows=1, ncols=2, gridspec_kw={'wspace': 0}) for subfig in subfigs.ravel()]
for subax in axs:
for ax in subax:
ax.plot(x, y)
ax.set_yticks([])
ax.set_xticks([])
plt.show()
With the current matplotlib 3.4.1, I don't seem to be able to have the inner plots without a gap. Setting constrained_layout=False even makes that the 4 rightmost subplots disappear. Now it looks like:
This is the goal of the new subfigure functionality: https://matplotlib.org/stable/gallery/subplots_axes_and_figures/subfigures.html?highlight=subfigure

Matplotlib.gridspec : how to specify the location by numbers?

I read the instruction in Customizing Location of Subplot Using GridSpec and try out the following codes and got the plot layout:
import matplotlib.gridspec as gridspec
gs = gridspec.GridSpec(3, 3)
ax1 = plt.subplot(gs[0, :])
ax2 = plt.subplot(gs[1, :-1])
ax3 = plt.subplot(gs[1:, -1])
ax4 = plt.subplot(gs[-1, 0])
ax5 = plt.subplot(gs[-1, -2])
I understand that gridspec.GridSpec(3, 3) will give a 3*3 layout, but what it means for gs[0, :] gs[1, :-1] gs[1:, -1] gs[-1, 0] gs[-1, -2]? I look up online but not found a detailed expanation, and I also try to change the index but not found a regular pattern. Could anyone give me some explanation or throw me a link about this?
Using gs = gridspec.GridSpec(3, 3), you have created essentially a 3 by 3 "grid" for your plots. From there, you can use gs[...,...] to specify the location and size of each subplot, by the number of rows and columns each subplot fills in that 3x3 grid. Looking in more detail:
gs[1, :-1] specifies where on the gridspace your subplot will be. For instance ax2 = plt.subplot(gs[1, :-1]) says: put the axis called ax2 on the first row (denoted by [1,...) (remember that in python, there is zero indexing, so this essentially means "second row down from the top"), stretching from the 0th column up until the last column (denoted by ...,:-1]). Because our gridspace is 3 columns wide, this means it will stretch 2 columns.
Perhaps it's better to show this by annotating each axis in your example:
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
gs = gridspec.GridSpec(3, 3)
ax1 = plt.subplot(gs[0, :])
ax2 = plt.subplot(gs[1, :-1])
ax3 = plt.subplot(gs[1:, -1])
ax4 = plt.subplot(gs[-1, 0])
ax5 = plt.subplot(gs[-1, -2])
ax1.annotate('ax1, gs[0,:] \ni.e. row 0, all columns',xy=(0.5,0.5),color='blue', ha='center')
ax2.annotate('ax2, gs[1, :-1]\ni.e. row 1, all columns except last', xy=(0.5,0.5),color='red', ha='center')
ax3.annotate('ax3, gs[1:, -1]\ni.e. row 1 until last row,\n last column', xy=(0.5,0.5),color='green', ha='center')
ax4.annotate('ax4, gs[-1, 0]\ni.e. last row, \n0th column', xy=(0.5,0.5),color='purple', ha='center')
ax5.annotate('ax5, gs[-1, -2]\ni.e. last row, \n2nd to last column', xy=(0.5,0.5), ha='center')
plt.show()

Categories