Setting x-axis frequency in table for Python - python

I have the following code that displays the numerical values of a matrix in a matplotlib.table object:
fig = plt.figure(figsize=(20,11))
plt.title('Correlation Matrix')
ticks = np.array(['$F_{sum}$','$F_{dif}$','$x_{sum}$','$x_{dif}$','$y_{sum}$','$y_{dif}$','$HLR_a$','$e1_a$','$e2_a$',
'$HLR_b$','$e1_b$','$e2_b$'])
ticks = ticks[::-1]
ticks = ticks.tolist()
plt.xticks([0.5,1.2,2.1,3.0,3.9,4.8,5.7,6.6,7.5,8.4,9.3,10],ticks,fontsize=15)
plt.yticks([0.5,1.2,2.1,3.0,3.9,4.8,5.7,6.6,7.5,8.4,9.3,10],['$F_{sum}$','$F_{dif}$','$x_{sum}$','$x_{dif}$','$y_{sum}$','$y_{dif}$','$HLR_a$','$e1_a$','$e2_a$',
'$HLR_b$','$e1_b$','$e2_b$'],fontsize=15)
round_mat = np.round(correlation_mat,2)
table = plt.table(cellText=round_mat,loc='center',colWidths=np.ones(correlation_mat.shape[0])/correlation_mat.shape[0],cellLoc='center',bbox=[0,0,1,1])
table.set_fontsize(25)
plt.show()
with the following output:
I want the x-axis and the y-axis ticks to be centered for each rectangle. Here, it seems that the first few ticks are correct and then the rest spread out. I would like them all equally spaced with the tick at the center. I am not sure what to do for this.

One way to do this is to use the row and column labels for the table. By default, they'll have a background and border, which is a touch clunky to turn off:
import numpy as np
import matplotlib.pyplot as plt
# Generate some data...
data = np.random.random((12, 10))
correlation_mat = np.cov(data)
correlation_mat /= np.diag(correlation_mat)
fig, ax = plt.subplots(figsize=(20,11))
ax.set_title('Correlation Matrix')
ticks = ['$F_{sum}$', '$F_{dif}$', '$x_{sum}$', '$x_{dif}$', '$y_{sum}$',
'$y_{dif}$', '$HLR_a$', '$e1_a$', '$e2_a$', '$HLR_b$', '$e1_b$',
'$e2_b$'][::-1]
round_mat = np.round(correlation_mat, 2)
table = ax.table(cellText=round_mat, cellLoc='center', bbox=[0, 0, 1, 1],
rowLabels=ticks, colLabels=ticks)
table.set_fontsize(25)
ax.axis('off')
for key, cell in table.get_celld().iteritems():
if key[0] == 0 or key[1] == -1:
cell.set(facecolor='none', edgecolor='none')
if key[1] == -1:
cell._loc = 'right'
elif key[0] == 0:
cell._loc = 'center'
plt.show()
However, it's sometimes easier to skip using a table for this altogether:
import numpy as np
import matplotlib.pyplot as plt
# Generate some data...
data = np.random.random((12, 10))
correlation_mat = np.cov(data)
correlation_mat /= np.diag(correlation_mat)
num = data.shape[0]
fig, ax = plt.subplots(figsize=(20,11))
ticks = ['$F_{sum}$', '$F_{dif}$', '$x_{sum}$', '$x_{dif}$', '$y_{sum}$',
'$y_{dif}$', '$HLR_a$', '$e1_a$', '$e2_a$', '$HLR_b$', '$e1_b$',
'$e2_b$']
ticks = ticks[::-1]
ax.matshow(correlation_mat, aspect='auto', cmap='cool')
ax.set(title='Correlation Matrix', xticks=range(num), xticklabels=ticks,
yticks=range(num), yticklabels=ticks)
ax.tick_params(labelsize=25)
for (i, j), val in np.ndenumerate(correlation_mat):
ax.annotate('{:0.2f}'.format(val), (j,i), ha='center', va='center', size=25)
plt.show()

Related

imshow subplot placement inside matplotlib figure

I have a Python script that draws a matrix of images, each image is read from disk and is 100x100 pixels. Current result is:
matrix of images
I don't know why Python adds vertical spacing between each row. I tried setting several parameters for plt.subplots. Rendering code is below:
fig, axs = plt.subplots(
gridRows, gridCols, sharex=True, sharey=False, constrained_layout={'w_pad': 0, 'h_pad': 0, 'wspace': 0, 'hspace': 0}, figsize=(9,9)
)
k = 0
for i in range(len(axs)):
for j in range(len(axs[i])):
if (k < paramsCount and dataset.iat[k,2]):
img = mpimg.imread(<some_folder_path>)
else:
img = mpimg.imread(<some_folder_path>)
ax = axs[i, j]
ax.imshow(img)
ax.axis('off')
if (i == 0): ax.set_title(dataset.iat[k,1])
if (j == 0): ax.text(-0.2, 0.5, dataset.iat[k,0], transform=ax.transAxes, verticalalignment='center', rotation='vertical', size=12)
axi = ax.axis()
rec = plt.Rectangle((axi[0], axi[2]), axi[1] - axi[0], axi[3] - axi[2], fill=False, lw=1, linestyle="dotted")
rec = ax.add_patch(rec)
rec.set_clip_on(False)
k = k + 1
plt.show()
Desired result is like:
desired result
Does anyone have ideas?
I'm sure there are many ways to do this other than the tashi answer, but the grid and subplot keywords are used in the subplot to remove the spacing and scale. In the loop process for each subplot, I set the graph spacing, remove the tick labels, and adjust the spacing by making the border dashed and the color gray. The title and y-axis labels are also added based on the loop counter value. Since the data was not provided, some of the data is written directly, so please replace it with your own data.
import matplotlib.pyplot as plt
import numpy as np
np.random.seed(20220510)
grid = np.random.rand(4, 4)
gridRows, gridCols = 5, 10
titles = np.arange(5,51,5)
ylabels = [500,400,300,200,100]
fig, axs = plt.subplots(gridRows, gridCols,
figsize=(8,4),
gridspec_kw={'wspace':0, 'hspace':0},
subplot_kw={'xticks': [], 'yticks': []}
)
for i, ax in enumerate(axs.flat):
ax.imshow(grid, interpolation='lanczos', cmap='viridis', aspect='auto')
ax.margins(0, 0)
if i < 10:
ax.set_title(str(titles[i]))
if i in [0,10,20,30,40]:
ax.set_ylabel(ylabels[int(i/10)])
ax.set_xticklabels([])
ax.set_yticklabels([])
for s in ['bottom','top','left','right']:
ax.spines[s].set_linestyle('dashed')
ax.spines[s].set_capstyle("butt")
for spine in ax.spines.values():
spine.set_edgecolor('gray')
plt.show()
I realized it has to do with the dimensions passed to figsize. Since rows count is half the columns count, I need to pass figsize(width, width/2).

How to modify xtick label of plt in Matplotlib

The objective is to modify the xticklabel upon plotting pcolormesh and scatter.
However, I am having difficulties accessing the existing xtick labels.
Simply
ax = plt.axes()
labels_x = [item.get_text() for item in ax.get_xticklabels()]
which produced:
['', '', '', '', '', '']
or
fig.canvas.draw()
xticks = ax.get_xticklabels()
which produced:
['', '', '', '', '', '']
does not return the corresponding label.
May I know how to properly access axis tick labels for a plt cases.
For readability, I split the code into two section.
The first section to generate the data used for plotting
Second section deal the plotting
Section 1: Generate data used for plotting
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import math
np.random.seed(0)
increment=120
max_val=172800
aran=np.arange(0,max_val,increment).astype(int)
arr=np.concatenate((aran.reshape(-1,1), np.random.random((aran.shape[0],4))), axis=1)
df=pd.DataFrame(arr,columns=[('lapse',''),('a','i'),('a','j'),('b','k'),('c','')])
ridx=df.index[df[('lapse','')] == 3600].tolist()[0]+1 # minus 1 so to allow 3600 start at new row
df[('event','')]=0
df.loc[[1,2,3,10,20,30],[('event','')]]=1
arr=df[[('a','i'),('event','')]].to_numpy()
col_len=ridx
v=arr[:,0].view()
nrow_size=math.ceil(v.shape[0]/col_len)
X=np.pad(arr[:,0].astype(float), (0, nrow_size*col_len - arr[:,0].size),
mode='constant', constant_values=np.nan).reshape(nrow_size,col_len)
mask_append_val=0 # This value must equal to 1 for masking
arrshape=np.pad(arr[:,1].astype(float), (0, nrow_size*col_len - arr[:,1].size),
mode='constant', constant_values=mask_append_val).reshape(nrow_size,col_len)
Section 2 Plotting
fig = plt.figure(figsize=(8,6))
plt.pcolormesh(X,cmap="plasma")
x,y = X.shape
xs,ys = np.ogrid[:x,:y]
# the non-zero coordinates
u = np.argwhere(arrshape)
plt.scatter(ys[:,u[:,1]].ravel()+.5,xs[u[:,0]].ravel()+0.5,marker='*', color='r', s=55)
plt.gca().invert_yaxis()
xlabels_to_use_this=df.loc[:30,[('lapse','')]].values.tolist()
# ax = plt.axes()
# labels_x = [item.get_text() for item in ax.get_xticklabels()]
# labels_y = [item.get_text() for item in ax.get_yticklabels()]
plt.xlabel('X-axis')
plt.ylabel('Y-axis')
plt.title("Plot 2D array")
plt.colorbar()
plt.tight_layout()
plt.show()
Expected output
This is how the plot could be generated using matplotlib's pcolormesh and scatter:
import matplotlib.pyplot as plt
from matplotlib.ticker import MultipleLocator
import pandas as pd
import numpy as np
np.random.seed(0)
increment = 120
max_val = 172800
aran = np.arange(0, max_val, increment).astype(int)
arr_df = np.concatenate((aran.reshape(-1, 1), np.random.random((aran.shape[0], 4))), axis=1)
df = pd.DataFrame(arr_df, columns=[('lapse', ''), ('a', 'i'), ('a', 'j'), ('b', 'k'), ('c', '')])
df[('event', '')] = 0
df.loc[[1, 2, 3, 10, 20, 30], [('event', '')]] = 1
col_len_lapse = 3600
col_len = df[df[('lapse', '')] == col_len_lapse].index[0]
nrow_size = int(np.ceil(v.shape[0] / col_len))
a_i_values = df[('a', 'i')].values
a_i_values_meshed = np.pad(a_i_values.astype(float), (0, nrow_size * col_len - len(a_i_values)),
mode='constant', constant_values=np.nan).reshape(nrow_size, col_len)
fig, ax = plt.subplots(figsize=(8, 6))
# the x_values indicate the mesh borders, subtract one half so the ticks can be at the centers
x_values = df[('lapse', '')][:col_len + 1].values - increment / 2
# divide lapses for y by col_len_lapse to get hours
y_values = df[('lapse', '')][::col_len].values / col_len_lapse - 0.5
y_values = np.append(y_values, 2 * y_values[-1] - y_values[-2]) # add the bottommost border (linear extension)
mesh = ax.pcolormesh(x_values, y_values, a_i_values_meshed, cmap="plasma")
event_lapses = df[('lapse', '')][df[('event', '')] == 1]
ax.scatter(event_lapses % col_len_lapse,
np.floor(event_lapses / col_len_lapse),
marker='*', color='red', edgecolor='white', s=55)
ax.xaxis.set_major_locator(MultipleLocator(increment * 5))
ax.yaxis.set_major_locator(MultipleLocator(5))
ax.invert_yaxis()
ax.set_xlabel('X-axis (s)')
ax.set_ylabel('Y-axis (hours)')
ax.set_title("Plot 2D array")
plt.colorbar(mesh)
plt.tight_layout() # fit the labels nicely into the plot
plt.show()
With Seaborn things can be simplified, adding new columns for hours and seconds, and using pandas' pivot (which automatically fills unavailable data with NaNs). Adding xtick_labels=5 sets the labels every 5 positions. (The star for lapse=3600 is at 1 hour, 0 seconds).
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
import numpy as np
# df created as before
df['hours'] = (df[('lapse', '')].astype(int) // 3600)
df['seconds'] = (df[('lapse', '')].astype(int) % 3600)
df_heatmap = df.pivot(index='hours', columns='seconds', values=('a', 'i'))
df_heatmap_markers = df.pivot(index='hours', columns='seconds', values=('event', '')).replace(
{0: '', 1: '★', np.nan: ''})
fig, ax = plt.subplots(figsize=(8, 6))
sns.heatmap(df_heatmap, xticklabels=5, yticklabels=5,
annot=df_heatmap_markers, fmt='s', annot_kws={'color': 'lime'}, ax=ax)
ax.tick_params(rotation=0)
plt.tight_layout()
plt.show()
Instead of a 'seconds' column, a 'minutes' column also might be interesting.
Here is an attempt to add time information as suggested in the comments:
from matplotlib import patheffects # to add some outline effect
# df prepared as the other seaborn example
fig, ax = plt.subplots(figsize=(8, 6))
path_effect = patheffects.withStroke(linewidth=2, foreground='yellow')
sns.heatmap(df_heatmap, xticklabels=5, yticklabels=5,
annot=df_heatmap_markers, fmt='s',
annot_kws={'color': 'red', 'path_effects': [path_effect]},
cbar=True, cbar_kws={'pad': 0.16}, ax=ax)
ax.tick_params(rotation=0)
ax2 = ax.twinx()
ax2.set_ylim(ax.get_ylim())
yticks = ax.get_yticks()
ax2.set_yticks(yticks)
ax2.set_yticklabels([str(pd.to_datetime('2019-01-15 7:00:00') + pd.to_timedelta(h, unit='h')).replace(' ', '\n')
for h in yticks])
I end up using Seaborn to address this issue.
Specifically, the following lines able to easily tweak the xticklabel
fig.canvas.draw()
new_ticks = [i.get_text() for i in g.get_xticklabels()]
i=[int(idx) for idx in new_ticks]
newlabel=xlabels_to_use_this[i]
newlabel=[np.array2string(x, precision=0) for x in newlabel]
The full code for plotting is as below
import seaborn as sns
fig, ax = plt.subplots()
sns.heatmap(X,ax=ax)
x,y = X.shape
xs,ys = np.ogrid[:x,:y]
# the non-zero coordinates
u = np.argwhere(arrshape)
g=sns.scatterplot(ys[:,u[:,1]].ravel()+.5,xs[u[:,0]].ravel()+0.5,marker='*', color='r', s=55)
fig.canvas.draw()
new_ticks = [i.get_text() for i in g.get_xticklabels()]
i=[int(idx) for idx in new_ticks]
newlabel=xlabels_to_use_this[i]
newlabel=[np.array2string(x, precision=0) for x in newlabel]
ax.set_xticklabels(newlabel)
ax.set_xticklabels(ax.get_xticklabels(),rotation = 90)
for ind, label in enumerate(g.get_xticklabels()):
if ind % 2 == 0: # every 10th label is kept
label.set_visible(True)
else:
label.set_visible(False)
for ind, label in enumerate(g.get_yticklabels()):
if ind % 4 == 0: # every 10th label is kept
label.set_visible(True)
else:
label.set_visible(False)
plt.xlabel('Elapsed (s)')
plt.ylabel('Hour (h)')
plt.title("Rastar Plot")
plt.tight_layout()
plt.show()

How to annotate and correctly place numbers in a heatmap

I'm having problems with heatmap.
I create the following function to show the analysis with heatmap
data = [ 0.00662896, -0.00213044, -0.00156812, 0.01450994, -0.00875174, -0.01561342, -0.00694762, 0.00476027, 0.00470659]
def plot_heatmap(pathOut, data, title, fileName, precis=2, show=False):
from matplotlib import cm
fig = plt.figure()
n = int(np.sqrt(len(data)))
data = data.reshape(n,n)
heatmap = plt.pcolor(data,cmap=cm.YlOrBr)
xLabels = (np.linspace(1,n,n,dtype=int))
yLabels = (np.linspace(1,n,n,dtype=int))
xpos = np.linspace(1,n,n)-0.5
ypos = np.linspace(1,n,n)-0.5
for y in range(n):
for x in range(n):
plt.text(x + 0.5, y + 0.5, f'{data[y, x]:.{precis}f}',
horizontalalignment='center',
verticalalignment='center',
)
plt.colorbar(heatmap, format='%.2f')
plt.xticks(xpos,xLabels)
plt.yticks(ypos,yLabels)
plt.title(f'{title}')
if (show == False ):
plt.close(fig)
elif (show == True):
plt.show()
fig.savefig(f'{pathOut}/{fileName}.pdf', format='pdf')
When I call the function the heatmap is created but not correctly, because I would like to show values at a specific precision. I know how to define text precision and scale precision, but how to adjust data precision to generate the correct heatmap?
In the attached figure, I have 7 cells equal to 0, for my desired precision, but the data used has a larger precision what produce different colors.
It is much easier to use seaborn.heatmap, which includes annotations and a colorbar. seaborn is a high-level API for matplotlib.
This significantly reduces the number of lines of code.
import matplotlib.pyplot as plt
from matplotlib import cm
import numpy as np
import seaborn as sns
def plot_heatmap(pathOut, fileName, data, title, precis=2, show=False):
n = int(np.sqrt(len(data)))
data = data.reshape(n, n)
xy_labels = range(1, n+1)
fig, ax = plt.subplots(figsize=(8, 6))
p = sns.heatmap(data=data, annot=True, fmt=f'.{precis}g', ax=ax,
cmap=cm.YlOrBr, xticklabels=xy_labels, yticklabels=xy_labels)
ax.invert_yaxis() # invert the axis if desired
ax.set_title(f'{title}')
fig.savefig(f'{pathOut}/{fileName}.pdf', format='pdf')
if (show == False ):
plt.close(fig)
elif (show == True):
plt.show()
data = np.array([ 0.00662896, -0.00213044, -0.00156812, 0.01450994, -0.00875174, -0.01561342, -0.00694762, 0.00476027, 0.00470659])
plot_heatmap('.', 'test', data, 'test', 4, True)
The f-string for plt.txt is not correct. It will be easier to round the value and convert it to a str type.
str(round(data[x, y], precis)) instead of f'{data[y, x]:.{precis}f}'
data[x, y] should be data[y, x]
import matplotlib.pyplot as plt
from matplotlib import cm
import numpy as np
def plot_heatmap(pathOut, fileName, data, title, precis=2, show=False):
fig = plt.figure(figsize=(8, 6))
n = int(np.sqrt(len(data)))
data = data.reshape(n, n)
heatmap = plt.pcolor(data, cmap=cm.YlOrBr)
xLabels = (np.linspace(1,n,n,dtype=int))
yLabels = (np.linspace(1,n,n,dtype=int))
xpos = np.linspace(1,n,n)-0.5
ypos = np.linspace(1,n,n)-0.5
for y in range(n):
for x in range(n):
s = str(round(data[y, x], precis)) # added s for plt.txt and reverse x and y for data addressing
plt.text(x + 0.5, y + 0.5, s,
horizontalalignment='center',
verticalalignment='center',
)
plt.colorbar(heatmap, format=f'%.{precis}f') # add precis to the colorbar
plt.xticks(xpos,xLabels)
plt.yticks(ypos,yLabels)
plt.title(f'{title}')
fig.savefig(f'{pathOut}/{fileName}.pdf', format='pdf') # this should be before plt.show()
if (show == False ):
plt.close(fig)
elif (show == True):
plt.show()
# the function expects an array, not a list
data = np.array([ 0.00662896, -0.00213044, -0.00156812, 0.01450994, -0.00875174, -0.01561342, -0.00694762, 0.00476027, 0.00470659])
# function call
plot_heatmap('.', 'test', data, 'test', 4, True)

How to set matplotlib's shared subplots legend to be horizontal and at lower center position?

I'm using Matplotlib and Seaborn to plot four bar graphs with one shared legend. However, I can't make the legend to be horizontal and at the lower center. I tried to set the numbers in this line:
ax.legend(bbox_to_anchor=(0.99, -0.15),
loc=1,
fontsize=13,
# ncol=2
)
but if the legend goes to the middle, then the distance between the two subplot columns would increase as well making it not good.
Here is my code:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import pdb
import pyautogui
import multiprocessing
from time import sleep
from matplotlib import patches as mpatches
def convert_to_grouped_bar_chart_format(data,
col_1_name, col_2_name, col_3_name):
"""
Parameters
----------
data: Pandas dataframe. Format:
Method Class1 Class2 Class3
0 Method_1 0.1 0.2 0.3
1 Method_2 0.6 0.5 0.4
Returns
-------
data_grouped: Pandas dataframe.
"""
cls_list = data.columns[1:].tolist()
col_1 = []
col_2 = []
col_3 = []
(num_of_rows, num_of_cols) = data.shape
for row_idx in range(num_of_rows):
for cls_idx, cls in enumerate(cls_list):
col_1.append(data.iloc[row_idx, 0])
col_2.append(cls)
col_3.append(data.iloc[row_idx, cls_idx+1])
pass
pass
data_grouped_dict = {
col_1_name: col_1,
col_2_name: col_2,
col_3_name: col_3
}
data_grouped = pd.DataFrame(data_grouped_dict, columns = [col_1_name, col_2_name, col_3_name])
return data_grouped
def draw_four_bar_graph_seaborn():
file_list = [
['Measure1_ED.csv', 'Measure1_ES.csv'],
['Measure2_ED.csv', 'Measure2_ES.csv']
]
n_rows = len(file_list)
n_cols = len(file_list[0])
fig, axes = plt.subplots(n_rows, n_cols)
for idx_row in range(n_rows):
# if idx_row > 0:
# continue
for idx_col in range(n_cols):
file_name = file_list[idx_row][idx_col]
data = pd.read_csv(file_name)
col_1_name = 'Method'
col_2_name = 'Class'
col_3_name = file_name.split('_')[0]
data_type = file_name.split('_')[1][:-4]
ax = axes[idx_row, idx_col]
# ax =axes[idx_col]
data_grouped = convert_to_grouped_bar_chart_format(data,
col_1_name, col_2_name, col_3_name)
splot = sns.barplot(
# ax=axes[idx_row, idx_col],
ax=ax,
x=col_2_name,
y=col_3_name,
hue=col_1_name,
palette="magma",
# palette=my_pal,
# sharey=False,
data=data_grouped)
splot.set_xlabel("",fontsize=1)
splot.set_ylabel(col_3_name,fontsize=13)
splot.tick_params(labelsize=13)
title_subplot = 'Title 1'
ax.set_title(title_subplot, fontsize=13)
if col_3_name == 'Measure1':
ax.set_ylim(0, 1.10)
else:
ax.set_ylim(0, 2.25)
for p1 in splot.patches:
splot.annotate('%.3f' % p1.get_height(),
(p1.get_x() + p1.get_width() / 2., p1.get_height()),
ha = 'center', va = 'center',
size=13,
xytext = (0, 8),
textcoords = 'offset points')
if (idx_row == 1) and (idx_col == 0):
ax.legend(
bbox_to_anchor=(1.2, -0.15),
loc=1,
fontsize=13,
# ncol=2
)
else:
splot.get_legend().remove()
# Change width size
# ax = axes[idx_row, idx_col]
new_value = 0.35
for patch in ax.patches :
current_width = patch.get_width()
diff = current_width - new_value
# we change the bar width
patch.set_width(new_value)
# we recenter the bar
patch.set_x(patch.get_x() + diff * .5)
plt.tight_layout(pad=0)
mng = plt.get_current_fig_manager()
mng.window.state('zoomed') #works fine on Windows!
plt.show()
fig.savefig('out.pdf')
plt.close()
def draw_graph_then_save_and_close_automatically(func=None, args=[]):
coords_close_graph = (1365, 12) # Auto click to close graph
multiprocessing.Process(target=func, args=args).start()
sleep(10)
pyautogui.moveTo(coords_close_graph)
pyautogui.click()
def main():
draw_graph_then_save_and_close_automatically(
func=draw_four_bar_graph_seaborn,
args=[])
if __name__ == '__main__':
main()
Please help me, thank you very much.
Use a figure-legend instead of place on on one of your axes and set the number of columns that the legend should have to the number of legend entries. Here is an example (I did find your's to be minimal enough^^)
import numpy as np
from matplotlib import pyplot as plt
# create random data
y = np.random.randint(0,100,size=(10, 3))
# open a figure with two axes
fig,axs = plt.subplots(1,2)
# plot something in the axes
axs[0].plot(y[:,0])
axs[1].plot(y[:,1:])
# define the name of the
legendEntries = ("a","bcdefg","h")
# set figure legend entries, number of columns, location
fig.legend(legendEntries,ncol=len(legendEntries),loc="upper center")
Here is a doc-example, emphasizing to use the argument ncol to force matplotlib to expand the legend horizontally. And here is a tutorial/example how you can place the legend of an axis outside the region of the axis.

How to plot an animated matrix in matplotlib

I need to do step by step some numerical calculation algorithms visually, as in the figure below: (gif)
Font
How can I do this animation with matplotlib? Is there any way to visually present these transitions? As transformation of matrices, sum, transposition, using a loop and it presenting the transitions etc.
My goal is not to use graphics but the same matrix representation. This is to facilitate the understanding of the algorithms.
Since matrices can be plotted easily with imshow, one could create such table with an imshow plot and adjust the data according to the current animation step.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as mticker
import matplotlib.animation
#####################
# Array preparation
#####################
#input array
a = np.random.randint(50,150, size=(5,5))
# kernel
kernel = np.array([[ 0,-1, 0], [-1, 5,-1], [ 0,-1, 0]])
# visualization array (2 bigger in each direction)
va = np.zeros((a.shape[0]+2, a.shape[1]+2), dtype=int)
va[1:-1,1:-1] = a
#output array
res = np.zeros_like(a)
#colorarray
va_color = np.zeros((a.shape[0]+2, a.shape[1]+2))
va_color[1:-1,1:-1] = 0.5
#####################
# Create inital plot
#####################
fig = plt.figure(figsize=(8,4))
def add_axes_inches(fig, rect):
w,h = fig.get_size_inches()
return fig.add_axes([rect[0]/w, rect[1]/h, rect[2]/w, rect[3]/h])
axwidth = 3.
cellsize = axwidth/va.shape[1]
axheight = cellsize*va.shape[0]
ax_va = add_axes_inches(fig, [cellsize, cellsize, axwidth, axheight])
ax_kernel = add_axes_inches(fig, [cellsize*2+axwidth,
(2+res.shape[0])*cellsize-kernel.shape[0]*cellsize,
kernel.shape[1]*cellsize,
kernel.shape[0]*cellsize])
ax_res = add_axes_inches(fig, [cellsize*3+axwidth+kernel.shape[1]*cellsize,
2*cellsize,
res.shape[1]*cellsize,
res.shape[0]*cellsize])
ax_kernel.set_title("Kernel", size=12)
im_va = ax_va.imshow(va_color, vmin=0., vmax=1.3, cmap="Blues")
for i in range(va.shape[0]):
for j in range(va.shape[1]):
ax_va.text(j,i, va[i,j], va="center", ha="center")
ax_kernel.imshow(np.zeros_like(kernel), vmin=-1, vmax=1, cmap="Pastel1")
for i in range(kernel.shape[0]):
for j in range(kernel.shape[1]):
ax_kernel.text(j,i, kernel[i,j], va="center", ha="center")
im_res = ax_res.imshow(res, vmin=0, vmax=1.3, cmap="Greens")
res_texts = []
for i in range(res.shape[0]):
row = []
for j in range(res.shape[1]):
row.append(ax_res.text(j,i, "", va="center", ha="center"))
res_texts.append(row)
for ax in [ax_va, ax_kernel, ax_res]:
ax.tick_params(left=False, bottom=False, labelleft=False, labelbottom=False)
ax.yaxis.set_major_locator(mticker.IndexLocator(1,0))
ax.xaxis.set_major_locator(mticker.IndexLocator(1,0))
ax.grid(color="k")
###############
# Animation
###############
def init():
for row in res_texts:
for text in row:
text.set_text("")
def animate(ij):
i,j=ij
o = kernel.shape[1]//2
# calculate result
res_ij = (kernel*va[1+i-o:1+i+o+1, 1+j-o:1+j+o+1]).sum()
res_texts[i][j].set_text(res_ij)
# make colors
c = va_color.copy()
c[1+i-o:1+i+o+1, 1+j-o:1+j+o+1] = 1.
im_va.set_array(c)
r = res.copy()
r[i,j] = 1
im_res.set_array(r)
i,j = np.indices(res.shape)
ani = matplotlib.animation.FuncAnimation(fig, animate, init_func=init,
frames=zip(i.flat, j.flat), interval=400)
ani.save("algo.gif", writer="imagemagick")
plt.show()
This example sets up the animation inline in a Jupyter notebook. I suppose there's probably also a way to export as a gif, but I haven't looked into that so far.
Anyway, first thing to do is set up the table. I borrowed heavily from Export a Pandas dataframe as a table image for the render_mpl_table code.
The (adapted) version for this problem is:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import animation
from IPython.display import HTML
import six
width = 8
data = pd.DataFrame([[0]*width,
[0, *np.random.randint(95,105,size=width-2), 0],
[0, *np.random.randint(95,105,size=width-2), 0],
[0, *np.random.randint(95,105,size=width-2), 0]])
def render_mpl_table(data, col_width=3.0, row_height=0.625, font_size=14,
row_color="w", edge_color="black", bbox=[0, 0, 1, 1],
ax=None, col_labels=data.columns,
highlight_color="mediumpurple",
highlights=[], **kwargs):
if ax is None:
size = (np.array(data.shape[::-1]) + np.array([0, 1])) *
np.array([col_width, row_height])
fig, ax = plt.subplots(figsize=size)
ax.axis('off')
mpl_table = ax.table(cellText=data.values, bbox=bbox, colLabels=col_labels,
**kwargs)
mpl_table.auto_set_font_size(False)
mpl_table.set_fontsize(font_size)
for k, cell in six.iteritems(mpl_table._cells):
cell.set_edgecolor(edge_color)
if k in highlights:
cell.set_facecolor(highlight_color)
elif data.iat[k] > 0:
cell.set_facecolor("lightblue")
else:
cell.set_facecolor(row_color)
return fig, ax, mpl_table
fig, ax, mpl_table = render_mpl_table(data, col_width=2.0, col_labels=None,
highlights=[(0,2),(0,3),(1,2),(1,3)])
In this case, the cells to highlight in a different color are given by an array of tuples that specify the row and column.
For the animation, we need to set up a function that draws the table with different highlights:
def update_table(i, *args, **kwargs):
r = i//(width-1)
c = i%(width-1)
highlights=[(r,c),(r,c+1),(r+1,c),(r+1,c+1)]
for k, cell in six.iteritems(mpl_table._cells):
cell.set_edgecolor("black")
if k in highlights:
cell.set_facecolor("mediumpurple")
elif data.iat[k] > 0:
cell.set_facecolor("lightblue")
else:
cell.set_facecolor("white")
return (mpl_table,)
This forcibly updates the colors for all cells in the table. The highlights array is computed based on the current frame. The width and height of the table are kind of hard-coded in this example, but that shouldn't be super hard to change based on the shape of your input data.
We create an animation based on the existing fig and update function:
a = animation.FuncAnimation(fig, update_table, (width-1)*3,
interval=750, blit=True)
And lastly we show it inline in our notebook:
HTML(a.to_jshtml())
I put this together in a notebook on github, see https://github.com/gurudave/so_examples/blob/master/mpl_animation.ipynb
Hope that's enough to get you going in the right direction!

Categories

Resources