I'm trying to dynamically append axes to a matplotlib figure. In my case, I don't know beforehand how many rows must be drawn/generated. So, In my case, I have a generator method that generates a row number, each call plot_history suppose to generate a row with two columns.
So it should incrementally append a row for each call. show() called later in the code
when all N sub_plot generated.
The logic in code that intended.
add ax1/ax2
ax1 = fig.add_subplot(row_num, 1, 1). ( row = 1, col 1 , pos 1)
ax2 = fig.add_subplot(row_num, 2, 2). ( row = 1, col 2, pos 2)
next call
ax1 = fig.add_subplot(row_num, 1, 1). ( row = 2, col 1 , pos 1)
ax2 = fig.add_subplot(row_num, 2, 2). ( row = 2, col 2, pos 2)
Question. I'm not sure maybe it is my misinterpretation of matplotlib doc. It looks like the code is correct but matplotlib show() doesn't render anything. (my understanding show() method generally blocking call so it should block at the end and render and add_subplot suppose append each ax_n to the same figure object). Can someone please explain?
Code below.
def plot_history(fig: matplotlib.figure.Figure, hs: History):
"""
:param fig: is matplotlib.figure.Figure
:param hs: Keras history
:return:
"""
# generator generate from 1 to infinity
row_num = next(row_num_generator)
ax1 = fig.add_subplot(row_num, 1, 1)
try:
ax1.plot(hs.history['accuracy'])
ax1.plot(hs.history['val_accuracy'])
except KeyError:
ax1.plot(hs.history['acc'])
ax1.plot(hs.history['val_acc'])
ax1.title.set_text('Accuracy vs. epochs')
ax1.set_ylabel('Loss')
ax1.set_xlabel('Epoch')
ax1.legend(['Training', 'Validation'], loc='lower right')
ax2 = fig.add_subplot(row_num, 2, 2)
ax2.title.set_text('Loss vs. epochs')
ax2.plot(hs.history['loss'])
ax2.plot(hs.history['val_loss'])
ax2.set_ylabel('Loss')
ax2.set_xlabel('Epoch')
ax2.legend(['Training', 'Validation'], loc='upper right')
def main():
main_figure = plt.figure()
# compute01 will do a work, call plot_history,
# it will add History and call it.
compute01(main_figure)
# compute02 will do a work, call plot_history,
# it will add History and call it.
compute02(main_figure)
...
...
compute0_N(main_figure)
main_figure.show()
Thank you very much.
Related
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]
Using proplot-0.9.5, the example code on https://proplot.readthedocs.io/en/latest/subplots.html (see below) gives an unexpected keyword argument 'refwidth' (see below also) error when trying to make custom scaled subplots.
Does anyone know why this doesn't work? Proplot does everything else I ask of it without a problem.
Code to reproduce the error:
import proplot as pplt
pplt.rc.update(grid=False, titleloc='uc', titleweight='bold', titlecolor='red9')
# Change the reference subplot width
suptitle = 'Effect of subplot width on figure size'
for refwidth in ('3cm', '5cm'):
fig, axs = pplt.subplots(ncols=2, refwidth=refwidth,)
axs[0].format(title=f'refwidth = {refwidth}', suptitle=suptitle)
# Change the reference subplot aspect ratio
suptitle = 'Effect of subplot aspect ratio on figure size'
for refaspect in (1, 2):
fig, axs = pplt.subplots(ncols=2, refwidth=1.6, refaspect=refaspect)
axs[0].format(title=f'refaspect = {refaspect}', suptitle=suptitle)
# Change the reference subplot
suptitle = 'Effect of reference subplot on figure size'
for ref in (1, 2): # with different width ratios
fig, axs = pplt.subplots(ncols=3, wratios=(3, 2, 2), ref=ref, refwidth=1.1)
axs[ref - 1].format(title='reference', suptitle=suptitle)
for ref in (1, 2): # with complex subplot grid
fig, axs = pplt.subplots([[1, 2], [1, 3]], refnum=ref, refwidth=1.8)
axs[ref - 1].format(title='reference', suptitle=suptitle)
pplt.rc.reset()
The error:
> TypeError: __init__() got an unexpected keyword argument 'refwidth'
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()
I am using Multicursor to get a cursor on every graph.
I want to show the value of the datapoint, which is hit by the cursor, inside a legend during hovering over the graphs, like this
Actually I have thought that this is a standard feature of matplotlib respectively Multicursor, but it seems not. Did someone already something like this or do I have to implement it by my own.
I already found this post matplotlib multiple values under cursor, but this could be just the beginning for the implementation I want.
I have developed a solution.
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.widgets import MultiCursor
from bisect import bisect_left
fig = plt.figure(figsize=(15, 8))
# create random graph with 60 datapoints, 0 till 59
x = list(range(0,60))
axes_list = []
def createRandomGraph(ax,x):
y = np.random.randint(low=0, high=15, size=60)
data.append(y)
ax.plot(x,y, marker='.')
def take_closest(myList, myNumber):
"""
Assumes myList is sorted. Returns closest value to myNumber.
If two numbers are equally close, return the smallest number.
"""
pos = bisect_left(myList, myNumber)
if pos == 0:
return myList[0]
if pos == len(myList):
return myList[-1]
before = myList[pos - 1]
after = myList[pos]
if after - myNumber < myNumber - before:
return after, pos
else:
return before, pos-1
def show_Legend(event):
#get mouse coordinates
mouseXdata = event.xdata
# the value of the closest data point to the current mouse position shall be shown
closestXValue, posClosestXvalue = take_closest(data[0], mouseXdata)
i = 1
for ax in axes_list:
datalegend = ax.text(1.05, 0.5, data[i][posClosestXvalue], fontsize=7,
verticalalignment='top', bbox=props, transform=ax.transAxes)
ax.draw_artist(datalegend)
# this remove is required because otherwise after a resizing of the window there is
# an artifact of the last label, which lies behind the new one
datalegend.remove()
i +=1
fig.canvas.update()
# store the x value of the graph in the first element of the list
data = [x]
# properties of the legend labels
props = dict(boxstyle='round', edgecolor='black', facecolor='wheat', alpha=1.5)
for i in range(5):
if(i>0):
# all plots share the same x axes, thus during zooming and panning
# we will see always the same x section of each graph
ax = plt.subplot(5, 1, i+1, sharex=ax)
else:
ax = plt.subplot(5, 1, i+1)
axes_list.append(ax)
createRandomGraph(ax,x)
multi = MultiCursor(fig.canvas, axes_list, color='r', lw=1)
# function show_Legend is called while hovering over the graphs
fig.canvas.mpl_connect('motion_notify_event', show_Legend)
plt.show()
The output looks like this
Maybe you like it and find it useful
I have been trying to achieve something like this:
Example
So far this is what I have tried:
crimes.Month = pd.to_datetime(crimes.Month, format='%Y/%m')
crimes.index = pd.DatetimeIndex(crimes.Month)
import numpy as np
crimes_count_date = crimes.pivot_table('Month', aggfunc=np.size,columns='Crime type', index=crimes.index.date, fill_value=0)
crimes_count_date.index = pd.DatetimeIndex(crimes_count_date.index)
plo = crimes_count_date.rolling(365).sum().plot(figsize=(12, 30),subplots=True, layout=(-1, 3), sharex=False, sharey=False)
Note- on the x-axis I would like to display each year/month: 2017/08
Current output below, is not showing all months/year or any lines for the crime types
Current Ouput
Not sure how your data looks.
But python has a nice way of doing subplots:
import matplotlib.pyplot as plt
plt.figure(figsize=(16,8)) ## setting over-all figure size (optional)
plt.subplot(2, 3, 1)
## this creates 6 subplots (2 rows and 3 columns)
## 1 at the end means we are in the first subplot.. then...
plt.plot(x1,y1) ## for well-selected x1 and y1
plt.subplot(232) ## the same as subplot(2, 3, 2) - you can use this when values have
## one digit only; now we are in the 2nd subplot
plt.plot(x2, y2) ## this will be plotted in the second subplot
## etc. ...
plt.subplot(236)
plt.plot(x6,y6)