Adding unique titles to subplots in matplotlib using a for loop - python-3.x

I have 8 subplots which I have created through a for loop, I've managed to add axis labels which was fine because these were identical across all charts. However my titles are different. Can anyone please tell me how to amend my for loop so it iterates through the titles? I have tried ax.set_title(titles) but this just lumps them all together rather than iterating through each one. I've also tried making the titles individual lists within a list but this didn't work either. The data relate to the Insanity Fit Test which is carried out 5 times throughout the Insanity program (in case you were wondering what the x axis was showing). My code is below:
fit_list = [[78.0, 94.0, 108.0, 117.0, 124.0], [40.0, 46.0, 48.0, 50.0, 50.0], [70.0, 90.0, 103.0, 100.0, 111.0],
[37.0, 38.0, 44.0, 55.0, 72.0], [5.0, 9.0, 8.0, 9.0, 9.0], [11.0, 15.0, 17.0, 18.0, 21.0],
[24.0, 30.0, 32.0, 34.0, 36.0], [35.0, 44.0, 50.0, 53.0, 64.0]]
x = [[1, 2, 3, 4, 5,], [1, 2, 3, 4, 5,], [1, 2, 3, 4, 5,], [1, 2, 3, 4, 5,],
[1, 2, 3, 4, 5,], [1, 2, 3, 4, 5,], [1, 2, 3, 4, 5,], [1, 2, 3, 4, 5,]]
y = fit_list
titles = ['Switch kicks', 'Power jacks', 'Power knees', 'Power jumps', 'Globe jumps', 'Suicide jumps',
'Push up jacks', 'Low plank obliques']
fig, axes = plt.subplots(nrows = 2, ncols = 4, figsize = (20, 10))
for exercise, ax in enumerate(axes.flatten()):
ax.bar(x[exercise], y[exercise], color = 'red')
ax.set_xlabel('Fit test event')
ax.set_ylabel('Number of reps')
plt.tight_layout()
Any help would be greatly appreciated. Thank you in advance.

One option could be the following:
for t, exercise in zip(titles, range(8)):
ax = axes.ravel()[exercise]
ax.bar(x[exercise], y[exercise], color = 'red')
ax.set_xlabel('Fit test event')
ax.set_ylabel('Number of reps')
ax.set_title(t)

Related

Using subplot2grid with 'AxesSubplot' object

I'm using subplot2grid to define a grid of plots as shown below.
Works great, it's a good functionality.
plot_axes_1 = plt.subplot2grid((6, 4), (0, 0), rowspan=2, colspan=3) ##1
plot_axes_2 = plt.subplot2grid((6, 4), (2, 0), rowspan=2, colspan=3, sharex=scatter_axes_1) ##2
x_hist_axes_2 = plt.subplot2grid((6, 4), (4, 0), colspan=3, sharex=scatter_axes_2) ##3
y_hist_axes_1 = plt.subplot2grid((6, 4), (0, 3), rowspan=2, sharey=scatter_axes_1) ##4
y_hist_axes_2 = plt.subplot2grid((6, 4), (2, 3), rowspan=2, sharey=scatter_axes_2, sharex= y_hist_axes_1) ##5
Now I want to consider the 5 plots from the image as a unit, and plot 6 copies of it, arranged on 3 rows and 2 columns.
fig, ax= plt.subplots(3,2)
for l in range(3):
for m in range(2):
ax[l,m].subplot2grid((6, 4), (0, 0), rowspan=2, colspan=3) ##1
ax[l,m].subplot2grid((6, 4), (2, 0), rowspan=2, colspan=3, sharex=scatter_axes_1) ##2
ax[l,m].subplot2grid((6, 4), (4, 0), colspan=3, sharex=scatter_axes_2) ##3
ax[l,m].subplot2grid((6, 4), (0, 3), rowspan=2, sharey=scatter_axes_1) ##4
ax[l,m].subplot2grid((6, 4), (2, 3), rowspan=2, sharey=scatter_axes_2, sharex= y_hist_axes_1) ##5
But I can't use subplot2grid like this, I get the error
'AxesSubplot' object has no attribute 'subplot2grid'
Is there another function I can use with AxesSubplot to do that?
I'm a little confused by what you are trying to do. However, a perhaps an alternate way to deal with different widths and heights is to use width ratios?
EDIT: use subfigure to keep logical groups of axes.
import matplotlib.pyplot as plt
fig = plt.figure(constrained_layout=True, figsize=(8, 12))
sfigs = fig.subfigures(3, 2)
for nn, sf in enumerate(sfigs.flat):
sf.suptitle(nn)
axs = sf.subplots(3, 2, gridspec_kw={'width_ratios': [2, 1],
'height_ratios': [2, 2, 1]})
sf.delaxes(axs[2, 1])
plt.show()
I think this is a job for matplotlib's sematic figure composition function, i.e., the subplot_mosaic function. This is available in matplotlib > 3.3. You will need to define a basic layout for your 5 panels, and then generate a full layout depending on how many rows/columns you want. As far as I can see, this will be quite convoluted and hard (although not impossible!) to create by subplot2grid or Gridspec or any of the other approaches.
import matplotlib.pyplot as plt
import numpy as np
def layout(panel, rows=3, cols=2, empty_sentinal=999):
"""Takes in a single layout and arranges it in multiple
rows and columns"""
npanels = rows * cols
panel[panel >= empty_sentinal] = empty_sentinal
minipanels = len(np.unique(panel))
panels = np.array([i * (minipanels) + panel for i in range(npanels)])
panel_rows = [np.hstack(panels[i : i + cols]) for i in range(0, npanels, cols)]
panel_cols = np.vstack(panel_rows)
panel_cols[panel_cols > empty_sentinal] = empty_sentinal
return panel_cols
A) Generating a single panel:
single_panel = np.array([
[1, 1, 1, 1, 1, 1, 2, 2, 999],
[1, 1, 1, 1, 1, 1, 2, 2, 999],
[1, 1, 1, 1, 1, 1, 2, 2, 999],
[1, 1, 1, 1, 1, 1, 2, 2, 999],
[3, 3, 3, 3, 3, 3, 4, 4, 999],
[3, 3, 3, 3, 3, 3, 4, 4, 999],
[3, 3, 3, 3, 3, 3, 4, 4, 999],
[3, 3, 3, 3, 3, 3, 4, 4, 999],
[5, 5, 5, 5, 5, 5, 999, 999, 999],
[5, 5, 5, 5, 5, 5, 999, 999, 999],
[5, 5, 5, 5, 5, 5, 999, 999, 999],
[999] * 9,
[999] * 9,
])
fig, ax = plt.subplot_mosaic(single_panel, figsize=(10, 10), empty_sentinel=999)
for k, v in ax.items():
v.set_xticklabels([])
v.set_yticklabels([])
v.text(0.5, 0.5, k, ha="center", va="center", fontsize=25)
plt.show()
(B) "Tiling" the above single panel
my_layout = layout(panel=single_panel, rows=3, cols=2)
fig, ax = plt.subplot_mosaic(my_layout, figsize=(10, 10), empty_sentinel=999)
for k, v in ax.items():
v.set_xticklabels([])
v.set_yticklabels([])
v.text(0.5, 0.5, k, ha="center", va="center", fontsize=25)
plt.show()
Some Notes:
The empty_sentinal is set to 999. If you have more than 999 subplots, increase that to a higher number.
Each "mini-panel" can be individually acessed. You might need to write other functions to access "panel-group"

how to change float 5.0 to number 5 in list of lists

I have the following list
X=[[[0, 'rating'], [1, 4.0], [2, 5.0], [1, 5.0], [0, 4.0], [8, 5.0], [3, 2.0], [5, 5.0], [4, 3.0], [2, 5.0]]]
y=[1, 1, 1, 1, 1, 0, 1, 0, 1, 1]
And I want to fit with sklearn.linear_model in order to classify and count the accuracy of the training data.
By using the following code
classifier = Perceptron(tol=1e-5, random_state=0)
classifier.fit(X,y)
I got this error: ValueError: could not convert string to float: 'rating'
I guess the problem is the float 5.0, but how can I simply change it? I tried with [[int(x) for x in x[1]]]

matplotlib graph shows up WITHOUT calling plt.show(), plt.show() doesn't display graph in separate cell in Jupyter Notebook

I got confused for the code I have here, when I develop my code into different cells in Jupyter Notebook, I gradually notice that the matplotlib graph shows up without calling plt.show(), which is anti-intuitive to me.
So if you implement the script below, the graph will pop up without plt.show():
data = np.array([
[5, 3, 2 ],
[2, -3, 5 ],
[ -4, 4, -6],
[-5, -3, -1],
[2, 6, 6]
])
bar_markers = np.array([4, 3, -2, 2, -1])
index = np.arange(len(data[:, 0]))
width = 0.15
fig, ax = plt.subplots()
ax.bar(index, data[:,0], width, bottom = 0, color = 'yellowgreen')
ax.bar(index+width, data[:, 1], width, bottom = 0, color = 'purple')
ax.bar(index+2*width, data[:, 2], width, bottom = 0, color = np.random.rand(3,))
ax.bar(index+width, [0]*len(bar_markers), width*3, bottom=bar_markers, edgecolor='k')
In a different word, this will cause confusion when you develop your code cell by cell in Jupyter Notebook, because 1)when you actually try to display graph using plt.show() in a different cell, it doesn't show any graph at all; 2) whenever you initialize graph using fig, ax = plt.subplots() it will display an empty graph right away. What I mean is shown below:
# in the first cell in Jupyter Notebook
data = np.array([
[5, 3, 2 ],
[2, -3, 5 ],
[ -4, 4, -6],
[-5, -3, -1],
[2, 6, 6]
])
bar_markers = np.array([4, 3, -2, 2, -1])
index = np.arange(len(data[:, 0]))
width = 0.15
fig, ax = plt.subplots()
Once implement it, the empty graph pops up without a reason.
# Then when you keep coding in a different cell
ax.bar(index, data[:,0], width, bottom = 0, color = 'yellowgreen')
ax.bar(index+width, data[:, 1], width, bottom = 0, color = 'purple')
ax.bar(index+2*width, data[:, 2], width, bottom = 0, color = np.random.rand(3,))
ax.bar(index+width, [0]*len(bar_markers), width*3, bottom=bar_markers, edgecolor='k')
plt.show()
It doesn't show anything at all.
If you know the reason why, please let us know, thank you in advance.

How do I get the x and y labels to appear when displaying more then one histogram using pandas hist() function with the by argument?

I am trying to create a series of graphs that share x and y labels. I can get the graphs to each have a label (explained well here!), but this is not what I am looking for.
I want one label that covers the y axis of both graphs, and same for the x axis.
I've been looking at the matplotlib and pandas documentation and I was unable to find anything that addresses this issues when the using by argument.
import matplotlib.pyplot as plt
import pandas as pd
df = pd.DataFrame({'A': [1, 2, 1, 2, 3, 4, 3, 4],
'B': [1, 7, 2, 4, 1, 4, 8, 3],
'C': [1, 4, 8, 3, 1, 7, 3, 4],
'D': [1, 2, 6, 5, 8, 3, 1, 7]},
index=[0, 1, 2, 3, 5, 6, 7, 8])
histo = df.hist(by=df['A'], sharey=True, sharex=True)
plt.ylabel('ylabel') # I assume the label is created on the 4th graph and then deleted?
plt.xlabel('xlabel') # Creates a label on the 4th graph.
plt.tight_layout()
plt.show()
The ouput looks like this.
Is there any way that I can create a Y Label that goes across the entire left side of the image (not each graph individually) and the same for the X Label.
As you can see, the x label only appears on the last graph created, and there is no y label.
Help?
This is one way to do it indirectly using the x- and y-labels as texts. I am not aware of a direct way using plt.xlabel or plt.ylabel. When passing an axis object to df.hist, the sharex and sharey arguments have to be passed in plt.subplots(). Here you can manually control/specify the position where you want to put the labels. For example, if you think the x-label is too close to the ticks, you can use 0.5, -0.02, 'X-label' to shift it slightly below.
import matplotlib.pyplot as plt
import pandas as pd
f, ax = plt.subplots(2, 2, figsize=(8, 6), sharex=True, sharey=True)
df = pd.DataFrame({'A': [1, 2, 1, 2, 3, 4, 3, 4],
'B': [1, 7, 2, 4, 1, 4, 8, 3],
'C': [1, 4, 8, 3, 1, 7, 3, 4],
'D': [1, 2, 6, 5, 8, 3, 1, 7]},
index=[0, 1, 2, 3, 5, 6, 7, 8])
histo = df.hist(by=df['A'], ax=ax)
f.text(0, 0.5, 'Y-label', ha='center', va='center', fontsize=20, rotation='vertical')
f.text(0.5, 0, 'X-label', ha='center', va='center', fontsize=20)
plt.tight_layout()
I fixed the issue with the variable number of sub-plots using something like this:
cols = 3
n = len(set(df['A']))
rows = int(n / cols) + (0 if n % cols == 0 else 1)
fig, axes = plt.subplots(rows, cols)
extra = rows * cols - n
if extra:
newaxes = []
count = 0
for row in range(rows):
for col in range(cols):
if count < n:
newaxes.append(axes[row][col])
else:
axes[row][col].axis('off')
count += 1
else:
newaxes = axes
hist = df.hist(by=df['A'], ax=newaxes)

Independent spacing for ticks and ticklabels?

I want to have a scatter plot with ticks as marginals:
x = [ 0, 1, 1.2, 1.3, 4, 5, 6, 7, 8.2, 9, 10]
y = [.2, .4, 2, 3, 4, 5, 5.1, 5.2, 4, 3, 8]
fig, ax1 = plt.subplots()
for spine in ax1.spines.values():
spine.set_visible(False)
ax1.scatter(x, y)
ax1.set_xticks(x)
ax1.set_xticklabels([])
ax1.set_yticks(y)
ax1.set_yticklabels([])
And on top of that, I want to have ticklabels at other positions, not determined by the ticks:
xticklabels = [0, 5, 10]
yticklabels = xticklabels
How could I possibly achieve that?
Matplotlib axes have major and minor ticks. You may use the minor ticks to show the marginal locations of the points. You may turn the major ticks off but show the ticklabels for them.
To set ticks at certain positions you can use a FixedLocator. To change the appearance of the ticks or turn them off, the axes has a tick_params method.
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
x = [ 0, 1, 1.2, 1.3, 4, 5, 6, 7, 8.2, 9, 10]
y = [.2, .4, 2, 3, 4, 5, 5.1, 5.2, 4, 3, 8]
xticklabels = [0, 5, 10]
yticklabels = xticklabels
fig, ax = plt.subplots()
for spine in ax.spines.values():
spine.set_visible(False)
ax.scatter(x, y)
ax.xaxis.set_major_locator(ticker.FixedLocator(xticklabels))
ax.yaxis.set_major_locator(ticker.FixedLocator(yticklabels))
ax.xaxis.set_minor_locator(ticker.FixedLocator(x))
ax.yaxis.set_minor_locator(ticker.FixedLocator(y))
ax.tick_params(axis="both", which="major", bottom="off", left="off")
ax.tick_params(axis="both", which="minor", length=4)
plt.show()
Note that I personally find this plot rather difficult to grasp and if I may, I would propose something more like this:
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
x = [ 0, 1, 1.2, 1.3, 4, 5, 6, 7, 8.2, 9, 10]
y = [.2, .4, 2, 3, 4, 5, 5.1, 5.2, 4, 3, 8]
xticklabels = [0, 5, 10]
yticklabels = xticklabels
fig, ax = plt.subplots()
ax.scatter(x, y)
ax.xaxis.set_minor_locator(ticker.FixedLocator(x))
ax.yaxis.set_minor_locator(ticker.FixedLocator(y))
c = "#aaaaaa"
ax.tick_params(axis="both", which="major", direction="out", color=c)
ax.tick_params(axis="both", which="minor", length=6, direction="in",
color="C0", width=1.5)
plt.setp(ax.spines.values(), color=c)
plt.setp(ax.get_xticklabels(), color=c)
plt.setp(ax.get_yticklabels(), color=c)
plt.show()

Resources