Group titles for matplotlib - python

I use a GridSpec within matplotlib to trying to generate the following plot:
However I fail at adding the titles at the desired positions, which are at the top center of each two columns. The following code creates the plot above sans titles:
fig = plt.figure(constrained_layout=True)
gs = fig.add_gridspec(2, 6)
for i in range(0, 6, 2):
fig.add_subplot(gs[:, i])
fig.add_subplot(gs[0, i + 1])
fig.add_subplot(gs[1, i + 1])
Adding the following two lines creates the titles but also creates a figure above the other figures:
title = fig.add_subplot(gs[:, i:i + 2])
title.set_title(f'title #{i}')
How do I have to change the given code to get the desired result depicted above? Is there a way to hide the new figures? Is there a way to set titles/text without figures?

One variant is to hide the newly added figures but their titles
Adding this line to the proposed other two, the plot looks like desired:
title.set_axis_off()
Therefore the full script would look like this:
fig = plt.figure(constrained_layout=True)
gs = fig.add_gridspec(2, 6)
for i in range(0, 6, 2):
fig.add_subplot(gs[:, i])
fig.add_subplot(gs[0, i + 1])
fig.add_subplot(gs[1, i + 1])
title = fig.add_subplot(gs[:, i:i + 2])
title.set_title(f'title #{i}')
title.set_axis_off()

Related

Annotating matplotlib heatmap y-axis ticks in monthly date increments

I am currently trying to create a heatmap to represent a set of data that is currently stored in a matrix of numerial values. However each row of the matrix is also tied to a specific date value, which I would like to represent on the y-axis tick labels. I currently have some code to do this that looks a little like this:
matrix = [[5, 9, 0, 4],
[0, 8, 3, 6],
[9, 1, 0, 4],
[0, 0, 3, 1]]
dates = [datetime.today() - timedelta(weeks=x * random.getrandbits(2)) for x in range(4)]
x_labels = ['Y29K', 'D950N', 'D142G', 'T95I']
title = 'Example Heatmap'
cbtitle = 'Mutation Count'
fig, ax = plt.subplots()
im = ax.imshow(matrix)
# Implement colorbar
max_val = round(int(np.max(matrix))/10)*10
color_bar_rnge = np.linspace(0,max_val,10, endpoint=False)
cb = fig.colorbar(im, ticks=color_bar_rnge)
cb.ax.set_title(cbtitle)
# Show all x axis ticks and label them with the respective list entries
ax.set_xticks(np.arange(len(x_labels)), labels=x_labels)
m_dates = mdates.date2num(dates)
ax.set_yticks(np.arange(len(m_dates)), labels=m_dates)
ax.yaxis_date()
# Rotate the tick labels and set their alignment.
plt.setp(ax.get_xticklabels(), rotation=90, ha="right",
rotation_mode="anchor")
dx, dy = -6, 0
offset = ScaledTranslation(dx / fig.dpi, dy / fig.dpi, fig.dpi_scale_trans)
for label in ax.xaxis.get_majorticklabels():
label.set_transform(label.get_transform() + offset)
ax.yaxis.set_major_formatter(mdates.DateFormatter('%Y-%m'))
ax.set_title(title)
fig.tight_layout()
fig.show()
However for some reason the resulting output seems to label dates on the y-axis as being in the 1970s. This issue only arrises when I implement:
ax.yaxis.set_major_formatter(mdates.DateFormatter('%Y-%m'))
However I would like to format my dates in this way as my real data set contains hundreds of dates (with multiple dates per month) and so labelling each tick with an individual date value creates an extremely overcrowded set of labels on the y-axis. Is there anyway to format just the date y_tick labels to be annotated in monthly increments, or does anyone know a potentially better solution to plot this sort of data?
Many thanks
One way to get rid of the 1970 year is to convert the randomly generated dates to string list and use that as the labels using set_yticklabels(). After you create the dates, you can convert the list of dates to string using dateLabels = [date.strftime('%Y-%m') for date in dates]. But, do note that all months are likely to be close or same. You can add -%d to see the date as well. Add ax.set_yticklabels(dateLabels) at the end to replace labels with this list.
Note that I have had to make a couple of other changes like set_xticks(), set_yticks() and split them to include set_xticklables(), set_yticklabels() as your code was not running on my version. Please do check if all your code is required, as some might not be required anymore.
Full code below.
import matplotlib.transforms as transforms
matrix = [[5, 9, 0, 4],
[0, 8, 3, 6],
[9, 1, 0, 4],
[0, 0, 3, 1]]
dates = [datetime.today() - timedelta(weeks=x * random.getrandbits(2)) for x in range(4)]
dateLabels = [date.strftime('%Y-%m') for date in dates]
print(dateLabels)
x_labels = ['Y29K', 'D950N', 'D142G', 'T95I']
title = 'Example Heatmap'
cbtitle = 'Mutation Count'
fig, ax = plt.subplots()
im = ax.imshow(matrix)
# Implement colorbar
max_val = round(int(np.max(matrix))/10)*10
color_bar_rnge = np.linspace(0,max_val,10, endpoint=False)
cb = fig.colorbar(im, ticks=color_bar_rnge)
cb.ax.set_title(cbtitle)
# Show all x axis ticks and label them with the respective list entries
ax.set_xticks(np.arange(len(x_labels)))
ax.set_xticklabels(x_labels)
m_dates = mdates.date2num(dates)
ax.set_yticks(np.arange(len(m_dates)))
ax.yaxis_date()
# Rotate the tick labels and set their alignment.
plt.setp(ax.get_xticklabels(), rotation=90, ha="right",
rotation_mode="anchor")
dx, dy = -6, 0
offset = transforms.ScaledTranslation(dx / fig.dpi, dy / fig.dpi, fig.dpi_scale_trans)
for label in ax.xaxis.get_majorticklabels():
label.set_transform(label.get_transform() + offset)
#ax.yaxis.set_major_formatter(mdates.DateFormatter('%Y-%m'))
ax.set_yticklabels(dateLabels)
ax.set_title(title)
fig.tight_layout()
fig.show()
Output
['2022-07', '2022-07', '2022-06', '2022-06']

Sizing figure with variable number of subplots and 2 legends

I'm having a really hard time attempting to properly size a figure with a variable number of subplots (between 3 and 8) and 2 legends that should appear glued to each other.
I also checked every related issue here in stack overflow, but couldn't get any answer to this specific case, due to my need for 2 legends.
The important to me is to get an optimal figure that I save as pdf to include in a report. I tried everything, and in the end the closes I got was with using tight: fig.savefig(f'variations{len(list_variations)}_B.pdf', bbox_inches='tight').
Here is a fully reproducible example (that emulates my code and figures):
list_variations = [0, 1, 2, 3, 4, 5, 6, 7, 8] # Does not work for any option
list_variations = [0, 1, 2] # Works Fine for Option A
n_subplots = len(list_variations)
fig_size = (5.457, n_subplots*3.5/3)
fig, axs = plt.subplots(n_subplots, 1, figsize=fig_size, sharex=True, sharey=True)
labels_upp = ('abdications', 'liner wint.ol.:$\\pm$0.19e', 'liner wint.ol.:$\\pm$0.1e')
labels_low = ('apportions', 'bisections', 'ablations', 'saktis')
for idx in list_variations:
for i, lab_upp in enumerate(labels_upp):
axs[idx].plot(60+i, 0.2, label=lab_upp)
for lab_low in labels_low:
axs[idx].plot(60+i, -0.2, label=lab_low)
axs[idx].set_title(f'Variation {idx}', fontsize=8)
axs[-1].set_xlim((60, 80))
axs[-1].set(ylim=(-1, 1))
axs[-1].set(xlabel='elasticity (e)')
plt.subplots_adjust(hspace=0.25)
# Make Legends (PROBLEM IS HERE)
# Option A - relative to fig
props_leg_upp = dict(facecolor='white', bbox_to_anchor=(0, -0.102, 1, 0.1), mode='expand', loc='upper center')
props_leg_low = dict(facecolor='lightgrey', bbox_to_anchor=(0, -0.172, 1, 0.1), mode='expand', loc='upper center')
upper_leg = fig.legend(labels_upp, ncol=len(labels_upp), **props_leg_upp)
lower_leg = fig.legend(labels_low, ncol=len(labels_low), **props_leg_low)
axs[-1].add_artist(upper_leg)
# Option B - relative to axs[-1]
props_leg_upp = dict(facecolor='white', bbox_to_anchor=(0, -0.262, 1, 0.1), mode='expand', loc='upper center')
props_leg_low = dict(facecolor='lightgrey', bbox_to_anchor=(0, -0.322, 1, 0.1), mode='expand', loc='upper center')
upper_leg = axs[-1].legend(labels_upp, ncol=len(labels_upp), **props_leg_upp)
lower_leg = axs[-1].legend(labels_low, ncol=len(labels_low), **props_leg_low)
axs[-1].add_artist(upper_leg)
I tried every combination of matplotlib.legend properties that I could think of, and in the end I got to these 2 options: A-apply the legend to figure; B-apply the legend to the last axis.
Option A works pretty well for 3 subplots:
In Option B (adding the legend to last axis), that I tried to force the legend to be the same width of the axis, the legends appear on top of each other (although I tried to finetune the bbox_to_anchor properties).
Yet, the biggest problem is when I use a greater number of subplots (e.g. 9 which is the maximum). For these case none of the options work.
Option A:
Option B:
Is there any way that I can make it work for different numbers of subplots, while (ideally) keeping the width of the legends the same as the width of the axis?
To align the legend in the subplot, I would need to set the transform coordinate axis of the legend box. In this case, the settings are added to match the last axis of the subplot. The box values were adjusted manually.
Since the box value parameters are bbox_to_anchor=(x0,y0,x1,y1), in this case y0,y1 are the same value.
import matplotlib.pyplot as plt
list_variations = [0, 1, 2, 3, 4, 5, 6, 7, 8] # Does not work for any option
#list_variations = [0, 1, 2] # Works Fine for Option A
n_subplots = len(list_variations)
fig_size = (5.457, n_subplots*3.5/3)
fig, axs = plt.subplots(n_subplots, 1, figsize=fig_size, sharex=True, sharey=True)
labels_upp = ('abdications', 'liner wint.ol.:$\\pm$0.19e', 'liner wint.ol.:$\\pm$0.1e')
labels_low = ('apportions', 'bisections', 'ablations', 'saktis')
for idx in list_variations:
for i, lab_upp in enumerate(labels_upp):
axs[idx].plot(60+i, 0.2, label=lab_upp)
for lab_low in labels_low:
axs[idx].plot(60+i, -0.2, label=lab_low)
axs[idx].set_title(f'Variation {idx}', fontsize=8)
axs[-1].set_xlim((60, 80))
axs[-1].set(ylim=(-1, 1))
axs[-1].set(xlabel='elasticity (e)')
plt.subplots_adjust(hspace=0.25)
# Make Legends (PROBLEM IS HERE)
# # Option A - relative to fig
props_leg_upp = dict(facecolor='white', bbox_to_anchor=(-0.1, -0.350, 1.2, 0.-0.350), mode='expand', loc='upper center')
props_leg_low = dict(facecolor='lightgrey', bbox_to_anchor=(-0.1, -0.650, 1.2, -0.650), mode='expand', loc='upper center')
upper_leg = fig.legend(labels_upp, ncol=len(labels_upp), bbox_transform=axs[-1].transAxes, **props_leg_upp)
lower_leg = fig.legend(labels_low, ncol=len(labels_low), bbox_transform=axs[-1].transAxes, **props_leg_low)
axs[-1].add_artist(upper_leg)
plt.show()
If you enable the following: list_variations = [0, 1, 2]

How to align several subplot in matplotlib?

I'd like to have four plots with images. All channels, and one for Red, Green and Blue channel. For RGB I'd like to plot a color profile across columns and rows. I would like to have the profile plots aligned exactly to the corresponding image. I mean their width and length should match to width and length, of an image. Their heights should be equal.
For now I achieved this effect using GridSpec. I know I can manipualte by changing fig size proportions, but it is not the solution. I need exact fit.
Here is my code.
def show_signal_across_image(self):
fig = plt.figure(figsize=(8, 9), dpi=109)
gs = GridSpec(4, 4,
height_ratios=[10, 2, 10, 2],
width_ratios=[10, 2, 10, 2],
left=0.05,
right=1 - 0.03,
top=0.95,
bottom=0.06,
hspace=0.3,
wspace=0.4)
ax_all = fig.add_subplot(gs[0])
ax_red_img = fig.add_subplot(gs[2])
ax_green_img = fig.add_subplot(gs[8])
ax_blue_img = fig.add_subplot(gs[10])
ax_red_signal_horizontal = fig.add_subplot(gs[6], sharex=ax_red_img)
ax_red_signal_vertical = fig.add_subplot(gs[3], sharey=ax_red_img)
ax_green_signal_horizontal = fig.add_subplot(gs[12], sharex=ax_green_img)
ax_green_signal_vertical = fig.add_subplot(gs[9], sharey=ax_green_img)
ax_blue_signal_horizontal = fig.add_subplot(gs[14], sharex=ax_blue_img)
ax_blue_signal_vertical = fig.add_subplot(gs[11], sharey=ax_blue_img)
signals = self.get_signal_values()
red_horizontal, red_vertical, green_horizontal, green_vertical, blue_horizontal, blue_vertical = signals
horizontal_signals = [red_horizontal, green_horizontal, blue_horizontal]
vertical_signals = [red_vertical, green_vertical, blue_vertical]
max_value_horizontal = max([item for sublist in horizontal_signals for item in sublist])
max_value_vertical = max([item for sublist in vertical_signals for item in sublist])
ax_red_signal_horizontal.plot(red_horizontal)
ax_green_signal_horizontal.plot(green_horizontal)
ax_blue_signal_horizontal.plot(blue_horizontal)
ax_red_signal_vertical.plot(red_vertical, np.arange(len(red_vertical)))
ax_green_signal_vertical.plot(green_vertical, np.arange(len(green_vertical)))
ax_blue_signal_vertical.plot(blue_vertical, np.arange(len(blue_vertical)))
ax_red_signal_vertical.invert_xaxis()
ax_green_signal_vertical.invert_xaxis()
ax_blue_signal_vertical.invert_xaxis()
for ax in [ax_red_signal_horizontal, ax_green_signal_horizontal, ax_blue_signal_horizontal]:
ax.set_ylim(0, max_value_horizontal * 1.1)
for ax in [ax_red_signal_vertical, ax_green_signal_vertical, ax_blue_signal_vertical]:
ax.set_xlim(max_value_vertical * 1.1, 0)
imshow_args = dict(vmin=0, vmax=1, cmap='gray')
plt.subplot(ax_all)
plt.title('All channels')
plt.imshow(self.img, **imshow_args)
plt.subplot(ax_red_img)
plt.title('Red')
plt.imshow(self.red, **imshow_args)
plt.subplot(ax_green_img)
plt.title('Green')
plt.imshow(self.green, **imshow_args)
plt.subplot(ax_blue_img)
plt.title('Blue')
plt.imshow(self.blue, **imshow_args)
plt.show()
I'm using matplotlib 3.1.1 and python 3.7.1.

Plot subplots using seaborn pairplot

If I draw the plot using the following code, it works and I can see all the subplots in a single row. I can specifically break the number of cols into three or two and show them. But I have 30 columns and I wanted to use a loop mechanism so that they are plotted in a grid of say 4x4 sub-plots
regressionCols = ['col_a', 'col_b', 'col_c', 'col_d', 'col_e']
sns.pairplot(numerical_df, x_vars=regressionCols, y_vars='price',height=4, aspect=1, kind='scatter')
plt.show()
The code using loop is below. However, I don't see anything rendered.
nr_rows = 4
nr_cols = 4
li_cat_cols = list(regressionCols)
fig, axs = plt.subplots(nr_rows, nr_cols, figsize=(nr_cols*4,nr_rows*4), squeeze=False)
for r in range(0, nr_rows):
for c in range(0,nr_cols):
i = r*nr_cols+c
if i < len(li_cat_cols):
sns.set(style="darkgrid")
bp=sns.pairplot(numerical_df, x_vars=li_cat_cols[i], y_vars='price',height=4, aspect=1, kind='scatter')
bp.set(xlabel=li_cat_cols[i], ylabel='Price')
plt.tight_layout()
plt.show()
Not sure what I am missing.
I think you didnt connect each of your subplot spaces in a matrix plot to scatter plots generated in a loop.
Maybe this solution with inner pandas plots could be proper for you:
For example,
1.Lets simply define an empty pandas dataframe.
numerical_df = pd.DataFrame([])
2. Create some random features and price depending on them:
numerical_df['A'] = np.random.randn(100)
numerical_df['B'] = np.random.randn(100)*10
numerical_df['C'] = np.random.randn(100)*-10
numerical_df['D'] = np.random.randn(100)*2
numerical_df['E'] = 20*(np.random.randn(100)**2)
numerical_df['F'] = np.random.randn(100)
numerical_df['price'] = 2*numerical_df['A'] +0.5*numerical_df['B'] - 9*numerical_df['C'] + numerical_df['E'] + numerical_df['D']
3. Define number of rows and columns. Create a subplots space with nr_rows and nr_cols.
nr_rows = 2
nr_cols = 4
fig, axes = plt.subplots(nrows=nr_rows, ncols=nr_cols, figsize=(15, 8))
for idx, feature in enumerate(numerical_df.columns[:-1]):
numerical_df.plot(feature, "price", subplots=True,kind="scatter",ax=axes[idx // 4,idx % 4])
4. Enumerate each feature in dataframe and plot a scatterplot with price:
for idx, feature in enumerate(numerical_df.columns[:-1]):
numerical_df.plot(feature, "price", subplots=True,kind="scatter",ax=axes[idx // 4,idx % 4])
where axes[idx // 4, idx % 4] defines the location of each scatterplot in a matrix you create in (3.)
So, we got a matrix plot:
Scatterplot matrix

ternary plots as subplot

I want to draw multiple ternary graphs and thought to do this using matplotlib's subplot.
I'm just getting empty 'regular' plots though, not the ternary graphs I want in there. I found the usage of
figure, ax = plt.subplots()
tax = ternary.TernaryAxesSubplot(ax=ax)
so this seems to be possible, but can't really find out how to get this working. Any ideas?
Code I'm using:
I'm using a for loop as the data has columns named tria1-a, tria2-a, etc for the different triads
import ternary
import matplotlib.pyplot as plt
import pandas as pd
#configure file to import.
filename = 'somecsv.csv'
filelocation = 'location'
dfTriad = pd.read_csv(filelocation+filename)
# plot the data
scale = 33
figure, ax = plt.subplots()
tax = ternary.TernaryAxesSubplot(ax=ax, scale=scale)
figure.set_size_inches(10, 10)
tax.set_title("Scatter Plot", fontsize=20)
tax.boundary(linewidth=2.0)
tax.gridlines(multiple=1, color="blue")
tax.legend()
tax.ticks(axis='lbr', linewidth=1, multiple=5)
tax.clear_matplotlib_ticks()
#extract the xyz columns for the triads from the full dataset
for i in range(1,6) :
key_x = 'tria'+ str(i) + '-a'
key_y = 'tria' + str(i) + '-b'
key_z = 'tria' + str(i) + '-c'
#construct dataframe from the extracted xyz columns
dfTriad_data = pd.DataFrame(dfTriad[key_x], columns=['X'])
dfTriad_data['Y'] = dfTriad[key_y]
dfTriad_data['Z'] = dfTriad[key_z]
#create list of tuples from the constructed dataframe
triad_data = [tuple(x) for x in dfTriad_data.to_records(index=False)]
plt.subplot(2, 3, i)
tax.scatter(triad_data, marker='D', color='green', label="")
tax.show()
I had the same problem and could solve it by first "going" into the subplot, then creating the ternary figure in there by giving plt.gca() as keyword argument ax:
plt.subplot(2,2,4, frameon = False)
scale = 10
plt.gca().get_xaxis().set_visible(False)
plt.gca().get_yaxis().set_visible(False)
figure, tax = ternary.figure(ax = plt.gca(), scale = scale)
#now you can use ternary normally:
tax.line(scale * np.array((0.5,0.5,0.0)), scale*np.array((0.0, 0.5, 0.5)))
tax.boundary(linewidth=1.0)
#...

Categories