Why won't matplotlib display the y-axis label on my tables? - python

I have tried around 15 different methods for setting the y-label for this simple confusion matrix visualization code. Currently, I have resorted to just directly labeling the rows as 'Predicted Positive' and 'Predicted Negative' but I would prefer to have 'Predicted' outside the table like I do with 'Actual'. Very confused what's going wrong. I'm assuming it has something to do with the fact that I'm plotting a table. Removing the row labels does not fix the issue. Thanks in advance!
def plot_conf_mat(data, model_name):
'''
Plot a confusion matrix based on the array data.
Expected: 2x2 matrix of form
[[TP, FP],
[FN, TN]].
Outputs a simple colored confusion matrix table
'''
#set fontsizes
SMALL_SIZE = 20
MEDIUM_SIZE = 25
BIGGER_SIZE = 30
plt.rc('font', size=MEDIUM_SIZE) # controls default text sizes
plt.rc('axes', titlesize=MEDIUM_SIZE) # fontsize of the axes title
plt.rc('axes', labelsize=SMALL_SIZE) # fontsize of the x and y labels
plt.rc('xtick', labelsize=SMALL_SIZE) # fontsize of the tick labels
plt.rc('ytick', labelsize=SMALL_SIZE) # fontsize of the tick labels
plt.rc('legend', fontsize=SMALL_SIZE) # legend fontsize
plt.rc('figure', titlesize=BIGGER_SIZE) # fontsize of the figure title
# Prepare table
columns = ('Positive', 'Negative')
rows = ('Predicted\nPositive', 'Predicted\nNegative')
cell_text = data
# Add a table at the bottom of the axes
colors = [["tab:green","tab:red"],[ "tab:red","tab:grey"]]
fig, ax = plt.subplots(figsize = (6,5))
ax.axis('tight')
ax.axis('off')
the_table = ax.table(cellText=cell_text,cellColours=colors,
colLabels=columns, rowLabels = rows, loc='center')
the_table.scale(2,5)
the_table.set_fontsize(20) #apparently it doesnt adhere to plt.rc??
ax.set_title(f'{model_name} Confusion Matrix: \n\nActual')
ax.set_ylabel('Predicted') #doesn't work!!
fig.savefig(f"{model_name}_conf_mat.pdf", bbox_inches = 'tight')
plt.show()
Out (model name redacted):

Firstly, did you know that there is a sklearn.metrics visualization option called ConfusionMatrixDisplay which might do what you are looking for. Do see if that helps.
For the table itself, matplotlib table is used to add a table to an axis. It usually contains a plot along with the table. As you only need a table, you are hiding the plot. If you comment out the line ax.axis('off'), you will see the borders of the plot. The ax.set_ylabel() will not work for this reason, as it is the label for the plot, which is hidden.
A simple workaround is to add text at the right place. Adding this instead of the set_ylabel() did the trick. You may need to fine tune the x and y coordinates.
plt.text(-0.155, -0.0275,'Predicted', fontsize= SMALL_SIZE, rotation=90)

Related

Displaying multiple graphs from networkx in a table

I've been playing around with the random graph feature of networkx as seen here with the Erdos-Renyi graph:
G = nx.gnp_random_graph(n, p, seed=None, directed=False)
I can then draw the graph with
nx.draw
Is there a way, I can make a table of random graph images using nx.draw? I want to make a table of some sampled graphs with some labels. Is there a way to do this using Matlab plot?
If I understand correclty, you can use subplots to achieve what you want:
fig, axes = plt.subplots(nrows=3, ncols=3)
for ax in axes.ravel():
G = nx.gnp_random_graph(10,10, seed=None, directed=False)
nx.draw_networkx(G, ax=ax)
Edit:
You can change the size of the figure at instantiation, by using:
fig, axes = plt.subplots(nrows=rows, ncols=cols, figsize=(10,10)) # default unit is inches.
You can change the size after the fact by doing:
fig.set_figwidth(10)
and
fig.set_figheight(10)
you can access individual subplots if you have more than 1 row and more than 1 column, like so:
axes[row,column] # zero-indexed.
to add labels or other stuff, you can do:
axes[row,column].set_ylabel('blah')
axes[row,column].set_title('blubb')
to change the figure title you can do:
fig.suptitle('my fancy title')
If at the end your labels intersect or your figure looks otherwise messy, you can enforce tight layout:
plt.tight_layout()

How can I rotate annotated seaborn heatmap data and legend?

I created to a seaborn heatmap to summarize Teils_U coefficients. The data is horizontally displayed in the heatmap. Now, I would like to rotate the data and the legend. I know that you can roate the x axis and y axis labels in a plot, but how can I rotate the data and the legend ?
This is my code:
#creates padnas dataframe to hold the values
theilu = pd.DataFrame(index=['Y'],columns=matrix.columns)
#store column names in variable columns
columns = matrix.columns
#iterate through each variable
for j in range(0,len(columns)):
#call teil_u function on "ziped" independant and dependant variable -> respectivley x & y in the functions section
u = theil_u(matrix['Y'].tolist(),matrix[columns[j]].tolist())
#select respecive columns needed for output
theilu.loc[:,columns[j]] = u
#handle nans if any
theilu.fillna(value=np.nan,inplace=True)
#plot correlation between fraud reported (y) and all other variables (x)
plt.figure(figsize=(20,1))
sns.heatmap(theilu,annot=True,fmt='.2f')
plt.show()
Here an image of what I am looking for:
Please let me know if you need and sample data or the teil_u function to recreate the problem. Thank you
The parameters of the annotation can be changed via annot_kws. One of them is the rotation.
Some parameters of the colorbar can be changed via cbar_kwsdict, but the unfortunately the orientation of the labels isn't one of them. Therefore, you need a handle to the colorbar's ax. One way is to create an ax beforehand, and pass it to sns.heatmap(..., cbar_ax=ax). An easier way is to get the handle afterwards: cbar = heatmap.collections[0].colorbar.
With this ax handle, you can change more properties of the colorbar, such as the orientation of its labels. Also, their vertical alignment can be changed to get them centered.
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
data = np.random.rand(1, 12)
fig, ax = plt.subplots(figsize=(10,2))
heatmap = sns.heatmap(data, cbar=True, ax=ax,
annot=True, fmt='.2f', annot_kws={'rotation': 90})
cbar = heatmap.collections[0].colorbar
# heatmap.set_yticklabels(heatmap.get_yticklabels(), rotation=90)
heatmap.set_xticklabels(heatmap.get_xticklabels(), rotation=90)
cbar.ax.set_yticklabels(cbar.ax.get_yticklabels(), rotation=90, va='center')
plt.tight_layout()
plt.show()
You can pass argument to ax.text() (which is used to write the annotation) using the annot_kws= argument.
Therefore:
flights = sns.load_dataset("flights")
flights = flights.pivot("month", "year", "passengers")
fig, ax = plt.subplots(figsize=(8,8))
ax = sns.heatmap(flights, annot=True, fmt='d', annot_kws={'rotation':90})

Changing the size of labels of plots in python

I am using GetDist for plotting contours in jupyter. I am wondering how to change the size of numbers in axes and labels of parameters.
There are some lines in the code containing labels as follows:
a,b,c = np.genfromtxt('data/data.txt',unpack=True)
names = ['H','M','z']
labels = ['H','M','z']
samples0 = MCSamples(samples=[a,b,c],names = names, labels = labels)
g.triangle_plot([samples0],['H','M','z'],legend_labels=['Summation of data'], legend_loc='upper right',filled=True)
The problem is when the number of parameter goes up, the plot should be smaller to placed in a printed paper and then we cannot see numbers and parameters' labels.
Thank you
I found the answer which is tricky
g.settings.axes_fontsize = 20
g.settings.lab_fontsize = 30
g.settings.x_label_rotation=47
g.settings.legend_fontsize = 40
by the use of g.setting in GetDist we can customize the plots.
you can use the plot.legend(loc=2, prop={'size': 6}) to increase the legend size This takes a dictionary of keywords corresponding to matplotlib.font_manager.FontProperties properties. more about legends
1). if you want to increase the size of the plotting data according to x values this would be helpful.
# yvalues is the y value list
widthscale = len(yvalues)/4
figsize = (8*widthscale,6) # fig size in inches (width,height)
figure = pylab.figure(figsize = figsize) # set the figsize
if you want increase them without dynamically you can use plot.rc function
eg.
import matplotlib.pyplot as plt
SMALL_SIZE = 8
MEDIUM_SIZE = 10
BIGGER_SIZE = 12
plt.rc('font', size=SMALL_SIZE) # controls default text sizes
plt.rc('axes', titlesize=SMALL_SIZE) # fontsize of the axes title
plt.rc('axes', labelsize=MEDIUM_SIZE) # fontsize of the x and y labels
plt.rc('xtick', labelsize=SMALL_SIZE) # fontsize of the tick labels
plt.rc('ytick', labelsize=SMALL_SIZE) # fontsize of the tick labels
plt.rc('legend', fontsize=SMALL_SIZE) # legend fontsize
plt.rc('figure', titlesize=BIGGER_SIZE) # fontsize of the figure title
2).second option would be
plt.rcParams["axes.labelsize"] = 22

or directly control the size of the label
ax.set_xlabel("some label", fontsize=22)
To control the legend's fontsize you can use rcParams
plt.rcParams["legend.fontsize"] = 22

or directly specify the size in the legend
ax.legend(fontsize=22)
You can change the font size of the labels to adjust them so they are more visible. If you can edit your question to include an MCVE by adding some dummy data and your plotting code, it will be much easier to provide more specific help.

Drawing a Table at the positions of yticks

I have been trouble with trying to find a way to display a 3 element list in the form of a table. What I actually care about is drawing the table. I would like to draw a 1by3 table for each ylabel in a plot.
Below is what I have so far. If I can get each Table instance to show up, I will have what I want. Right now a reference to a table appears and I'm not sure why. If you actually look in the center left where the reference locations appear, you can see one 1by3 table.
Is it possible using matplotlib to generate a new table for each ylabel? The table info is directly related to each row in the bar graph, so it's important that I have a way that they line up.
The number of rows in the bar graph is dynamic, so creating 1 table for the whole figure and trying to dynamically line up the rows with the corresponding bar graph is a difficult problem.
# initialize figure
fig = plt.figure()
gs = gridspec.GridSpec(1, 2, width_ratios=[2, 1])
fig.set_size_inches(18.5, 10.5)
ax = fig.add_subplot(gs[0])
#excluded bar graph code
# create ylabels
for row in range(1,len(data)):
ylabel = [str(data[row][0]),str(data[row][1]),str(data[row][2])]
ylabels.append(ylabel)
#attempting to put each ylabel in a 1by3 table to display
pos = np.arange(0.5,10.5,0.5)
axTables = [None] * len(ylabels)
for x in range(0,len(ylabels)):
axTables[x] = fig.add_subplot(gs[0])
ylabels[x] = axTables[x].table(cellText=[ylabels[x]], loc='left')
locsy, labelsy = plt.yticks(pos,ylabels)
First, yticks will expect text as input, it cannot handle other objects. Second, a table needs to sit within an axes.
So in order to get a table at the position of a tick(label) the idea can be to create an axes at the position of a y ticklabel. An option is the use of mpl_toolkits.axes_grid1.inset_locator.inset_axes. Now the difficulty is that this axes needs to be positionned in data coordinates along the y axis, and in figure (or pixel-) coorinates in the horizontal direction. For this one might use a blended transform. The inset_axes allows to give the width and height as absolute measures (in inches) or in relative, which is handy because we can set the width of the axes to 100% of the bounding box, while the height is still some absolute value (we don't want the axes height to depend on the data coordinates!).
In the following a function ax_at_posy creates such axes.
The table would then sit tight inside the axes, such that all columns are the same width. One would still need to make sure the same fontsize is used throughout.
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1.inset_locator import inset_axes
import matplotlib.transforms as mtrans
# General helper function to create an axes at the position of a yticklabel
def ax_at_posy(y, ax=None, width=0.3, leftspace=0.08, height=0.2):
ax = ax or plt.gca()
trans = mtrans.blended_transform_factory(ax.figure.transFigure, ax.transData)
axins = inset_axes(ax, "100%", height,
bbox_to_anchor=(leftspace, y, width-leftspace, 0.05),
bbox_transform=trans, loc="center right", borderpad=0.8)
axins.tick_params(left=False, bottom=False, labelleft=False, labelbottom=False)
axins.axis("off")
return axins
fig, ax = plt.subplots()
fig.subplots_adjust(left=0.4)
ax.scatter(np.random.rand(30), np.random.randint(7, size=30), c=np.random.rand(30))
get_data = lambda i: "".join(np.random.choice(list("abcdefgxyzt0"), size=i+2))
data = np.vectorize(get_data)(np.random.randint(2,6,size=(7,3)))
for i, row in enumerate(data):
axi = ax_at_posy(i, ax=ax, width=0.4)
tab = axi.table(cellText=[list(row)], loc='center', bbox=(0,0,1,1))
tab.auto_set_font_size(False)
tab.set_fontsize(9)
plt.setp(tab.get_celld().values(), linewidth=0.72)
plt.show()

How to change the positions of subplot titles and axis labels in Seaborn FacetGrid?

I am trying to plot a polar plot using Seaborn's facetGrid, similar to what is detailed on seaborn's gallery
I am using the following code:
sns.set(context='notebook', style='darkgrid', palette='deep', font='sans-serif', font_scale=1.25)
# Set up a grid of axes with a polar projection
g = sns.FacetGrid(df_total, col="Construct", hue="Run", col_wrap=5, subplot_kws=dict(projection='polar'), size=5, sharex=False, sharey=False, despine=False)
# Draw a scatterplot onto each axes in the grid
g.map(plt.plot, 'Rad', ''y axis label', marker=".", ms=3, ls='None').set_titles("{col_name}")
plt.savefig('./image.pdf')
Which with my data gives the following:
I want to keep this organisation of 5 plots per line.
The problem is that the title of each subplot overlap with the values of the ticks, same for the y axis label.
Is there a way to prevent this behaviour? Can I somehow shift the titles slightly above their current position and can I shift the y axis labels slightly on the left of their current position?
Many thanks in advance!
EDIT:
This is not a duplicate of this SO as the problem was that the title of one subplot overlapped with the axis label of another subplot.
Here my problem is that the title of one subplot overlaps with the ticks label of the same subplot and similarly the axis label overlaps with the ticks label of the same subplot.
I also would like to add that I do not care that they overlap on my jupyter notebook (as it as been created with it), however I want the final saved image with no overlap, so perhaps there is something I need to do to save the image in a slightly different format to avoid that, but I don't know what (I am only using plt.savefig to save it).
EDIT 2: If someone would like to reproduce the problem here is a minimal example:
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns
sns.set()
sns.set(context='notebook', style='darkgrid', palette='deep', font='sans-serif', font_scale=1.5)
# Generate an example radial datast
r = np.linspace(0, 10000, num=100)
df = pd.DataFrame({'label': r, 'slow': r, 'medium-slow': 1 * r, 'medium': 2 * r, 'medium-fast': 3 * r, 'fast': 4 * r})
# Convert the dataframe to long-form or "tidy" format
df = pd.melt(df, id_vars=['label'], var_name='speed', value_name='theta')
# Set up a grid of axes with a polar projection
g = sns.FacetGrid(df, col="speed", hue="speed",
subplot_kws=dict(projection='polar'), size=4.5, col_wrap=5,
sharex=False, sharey=False, despine=False)
# Draw a scatterplot onto each axes in the grid
g.map(plt.scatter, "theta", "label")
plt.savefig('./image.png')
plt.show()
Which gives the following image in which the titles are not as bad as in my original problem (but still some overlap) and the label on the left hand side overlap completely.
In order to move the title a bit higher you can set at new position,
ax.title.set_position([.5, 1.1])
In order to move the ylabel a little further left, you can add some padding
ax.yaxis.labelpad = 25
To do this for the axes of the facetgrid, you'd do:
for ax in g.axes:
ax.title.set_position([.5, 1.1])
ax.yaxis.labelpad = 25
The answer provided by ImportanceOfBeingErnest in this SO question may help.

Categories