group multiple plot in one figure python - python

My function return 28 plots ( figure) but i need to group them on one figure this is my code for generating 28 plots
for cat in df.ASS_ASSIGNMENT.unique() :
a = df.loc[df['ASS_ASSIGNMENT'] == cat]
dates = a['DATE']
prediction = a['CSPL_RECEIVED_CALLS']
plt.plot(dates,prediction)
plt.ylabel("nmb_app")
plt.legend([cat.decode('utf-8')],loc='best')
plt.xlabel(cat.decode('utf-8'))

Use plt.subplots. For example,
import numpy as np
import matplotlib.pyplot as plt
fig, axes = plt.subplots(ncols=7, nrows=4)
for i, ax in enumerate(axes.flatten()):
x = np.random.randint(-5, 5, 20)
y = np.random.randint(-5, 5, 20)
ax.scatter(x, y)
ax.set_title('Axis {}'.format(i))
plt.tight_layout()
Going a little deeper, as Mauve points out, it depends if you want 28 curves in a single plot in a single figure or 28 individual plots each with its own axis all in one figure.
Assuming you have a dataframe, df, with 28 columns you can put all 28 curves on a single plot in a single figure using plt.subplots like so,
fig1, ax1 = plt.subplots()
df.plot(color=colors, ax=ax1)
plt.legend(ncol=4, loc='best')
If instead you want 28 individual axes all in one figure you can use plt.subplots this way
fig2, axes = plt.subplots(nrows=4, ncols=7)
for i, ax in enumerate(axes.flatten()):
df[df.columns[i]].plot(color=colors[i], ax=ax)
ax.set_title(df.columns[i])
Here df looks like
In [114]: df.shape
Out[114]: (15, 28)
In [115]: df.head()
Out[115]:
IYU ZMK DRO UIC DOF ASG DLU \
0 0.970467 1.026171 -0.141261 1.719777 2.344803 2.956578 2.433358
1 7.982833 7.667973 7.907016 7.897172 6.659990 5.623201 6.818639
2 4.608682 4.494827 6.078604 5.634331 4.553364 5.418964 6.079736
3 1.299400 3.235654 3.317892 2.689927 2.575684 4.844506 4.368858
4 10.690242 10.375313 10.062212 9.150162 9.620630 9.164129 8.661847
BO1 JFN S9Q ... X4K ZQG 2TS \
0 2.798409 2.425745 3.563515 ... 7.623710 7.678988 7.044471
1 8.391905 7.242406 8.960973 ... 5.389336 5.083990 5.857414
2 7.631030 7.822071 5.657916 ... 2.884925 2.570883 2.550461
3 6.061272 4.224779 5.709211 ... 4.961713 5.803743 6.008319
4 10.240355 9.792029 8.438934 ... 6.451223 5.072552 6.894701
RS0 P6T FOU LN9 CFG C9D ZG2
0 9.380106 9.654287 8.065816 7.029103 7.701655 6.811254 7.315282
1 3.931037 3.206575 3.728755 2.972959 4.436053 4.906322 4.796217
2 3.784638 2.445668 1.423225 1.506143 0.786983 -0.666565 1.120315
3 5.749563 7.084335 7.992780 6.998563 7.253861 8.845475 9.592453
4 4.581062 5.807435 5.544668 5.249163 6.555792 8.299669 8.036408
and was created by
import pandas as pd
import numpy as np
import string
import random
m = 28
n = 15
def random_data(m, n):
return np.cumsum(np.random.randn(m*n)).reshape(m, n)
def id_generator(number, size=6, chars=string.ascii_uppercase + string.digits):
sequence = []
for n in range(number):
sequence.append(''.join(random.choice(chars) for _ in range(size)))
return sequence
df = pd.DataFrame(random_data(n, m), columns=id_generator(number=m, size=3))
Colors was defined as
import seaborn as sns
colors = sns.cubehelix_palette(28, rot=-0.4)

Related

Not able to use plt.subplots() for my data [duplicate]

This question already has answers here:
How to plot in multiple subplots
(12 answers)
Closed 1 year ago.
I am using the Lombscargle function to output the power spectrum for a signal I pass as input, I am able to get the plots one after another but the task at hand is to plot these graphs using subplots in a way that there are 5 rows, 4 cols.
An example for signal would be:
signal = [ '254.24', '254.32', '254.4', '254.84', '254.24', '254.28', '254.84', '253.56', '253.76', '253.32', '253.88', '253.72', '253.92', '251.56', '253.04', '244.72', '243.84', '246.08', '245.84', '249.0', '250.08', '248.2', '253.12', '253.2', '253.48', '253.88', '253.12', '253.4', '253.4']
from scipy.signal import lombscargle
def LSP_scipy(signal):
start_ang_freq = 2 * np.pi * (60/60)
end_ang_freq = 2 * np.pi * (240/60)
SAMPLES = 5000
SAMPLE_SPACING = 1/15
t = np.linspace(0,len(signal)*SAMPLE_SPACING,len(signal))
period_freq = np.linspace(start_ang_freq,end_ang_freq,SAMPLES)
modified_signal_axis = []
modified_time_axis = []
for count,value in enumerate(signal):
if value != 'None':
modified_signal_axis.append(float(value))
modified_time_axis.append(t[count])
prog = lombscargle(modified_time_axis, modified_signal_axis, period_freq, normalize=False, precenter = True)
fig, axes = plt.subplots()
ax.plot(period_freq,prog)
How do I plot these graphs in a matrix format?
Trying loop approach,
See inline comments to add and flatten the subplots.
This is an implementation of flattening the axes array from this answer of the duplicate.
from scipy.signal import lombscargle
from matplotlib.ticker import FormatStrFormatter
import numpy as np
import matplotlib.pyplot as plt
def LSP_scipy(signal):
start_ang_freq = 2 * np.pi * (60/60)
end_ang_freq = 2 * np.pi * (240/60)
SAMPLES = 5000
SAMPLE_SPACING = 1/15
t = np.linspace(0, len(signal)*SAMPLE_SPACING, len(signal))
period_freq = np.linspace(start_ang_freq, end_ang_freq, SAMPLES)
modified_signal_axis = []
modified_time_axis = []
# create the figure and subplots
fig, axes = plt.subplots(5, 6, figsize=(20, 9), sharex=False, sharey=False)
# flatten the array
axes = axes.ravel()
for count, value in enumerate(signal):
if value != 'None':
modified_signal_axis.append(float(value))
modified_time_axis.append(t[count])
prog = lombscargle(modified_time_axis, modified_signal_axis, period_freq, normalize=False, precenter=True)
# plot
axes[count].plot(period_freq, prog)
# format the axes
axes[count].set(title=value)
# some plot have an exponential offset on the yaxis, this turns it off
axes[count].ticklabel_format(useOffset=False)
# some yaxis values are long floats, this formats them to 3 decimal places
axes[count].yaxis.set_major_formatter(FormatStrFormatter('%.3f'))
# format the figure
fig.tight_layout()
signal = [ '254.24', '254.32', '254.4', '254.84', '254.24', '254.28', '254.84', '253.56', '253.76', '253.32', '253.88', '253.72', '253.92', '251.56', '253.04', '244.72', '243.84', '246.08', '245.84', '249.0', '250.08', '248.2', '253.12', '253.2', '253.48', '253.88', '253.12', '253.4', '253.4']
LSP_scipy(signal[:20]) # as per comment, only first 20
You can use for loop and iterate over subplots. A very simple example is shown below.The subplots method creates the figure along with the subplots and store in the ax array.
import matplotlib.pyplot as plt
x = np.linspace(0, 10)
y = range(10)
fig, ax = plt.subplots(nrows=2, ncols=2)
for row in ax:
for col in row:
col.plot(x, y)
plt.show()
# or you can also do
for in range(2): # row=0, col=0
for j in range(2): # row=0, col=1
ax[i, j].plot(x,y) # row=1, col=0
# row=1, col=1
Then one idea is to take the signals into an array of shape=(20,1), where each row corresponds to signal amplitude or some other measurable quantity. Then you could do as below (check the output keeping only the lines till plt.text you will get the idea).
for i in range(1, 21):
plt.subplot(5, 4, i)
plt.text(0.5, 0.5, str((5, 4, i)),
fontsize=18, ha='center')
# Call the function here...get the value of period_freq and prog
period_freq,prog = LSP_scipy(signal[i])
plt.plot(period_freq, prog)

How to plot a list of figures in a single subplot?

I have 2 lists of figures and their axes.
I need to plot each figure in a single subplot so that the figures become in one big subplot. How can I do that?
I tried for loop but it didn't work.
Here's what I have tried:
import ruptures as rpt
import matplotlib.pyplot as plt
# make random data with 100 samples and 9 columns
n_samples, n_dims, sigma = 100, 9, 2
n_bkps = 4
signal, bkps = rpt.pw_constant(n_samples, n_dims, n_bkps, noise_std=sigma)
figs, axs = [], []
for i in range(signal.shape[1]):
points = signal[:,i]
# detection of change points
algo = rpt.Dynp(model='l2').fit(points)
result = algo.predict(n_bkps=2)
fig, ax = rpt.display(points, bkps, result, figsize=(15,3))
figs.append(fig)
axs.append(ax)
plt.show()
I had a look at the source code of ruptures.display(), and it accepts **kwargs that are passed on to matplotlib. This allows us to redirect the output to a single figure, and with gridspec, we can position individual subplots within this figure:
import ruptures as rpt
import matplotlib.pyplot as plt
n_samples, n_dims, sigma = 100, 9, 2
n_bkps = 4
signal, bkps = rpt.pw_constant(n_samples, n_dims, n_bkps, noise_std=sigma)
#number of subplots
n_subpl = signal.shape[1]
#give figure a name to refer to it later
fig = plt.figure(num = "ruptures_figure", figsize=(8, 15))
#define grid of nrows x ncols
gs = fig.add_gridspec(n_subpl, 1)
for i in range(n_subpl):
points = signal[:,i]
algo = rpt.Dynp(model='l2').fit(points)
result = algo.predict(n_bkps=2)
#rpt.display(points, bkps, result)
#plot into predefined figure
_, curr_ax = rpt.display(points, bkps, result, num="ruptures_figure")
#position current subplot within grid
curr_ax[0].set_position(gs[i].get_position(fig))
curr_ax[0].set_subplotspec(gs[i])
plt.show()
Sample output:

How to plot a histogram for all unique combinations of data?

Is there a way I can get a size frequency histogram for a population under different scenarios for specific days in python
means with error bars
My data are in a format similar to this table:
SCENARIO RUN MEAN DAY
A 1 25 10
A 1 15 30
A 2 20 10
A 2 27 30
B 1 45 10
B 1 50 30
B 2 43 10
B 2 35 30
results_data.groupby(['Scenario', 'Run']).mean() does not give me the days I want to visualize the data by
it returns the mean on the days in each run.
Use seaborn.FacetGrid
FactGrid is a Multi-plot grid for plotting conditional relationships
Map seaborn.distplot onto the FacetGrid and use hue=DAY.
Setup Data and DataFrame
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
import random # just for test data
import numpy as np # just for test data
# data
random.seed(365)
np.random.seed(365)
data = {'MEAN': [np.random.randint(20, 51) for _ in range(500)],
'SCENARIO': [random.choice(['A', 'B']) for _ in range(500)],
'DAY': [random.choice([10, 30]) for _ in range(500)],
'RUN': [random.choice([1, 2]) for _ in range(500)]}
# create dataframe
df = pd.DataFrame(data)
Plot with kde=False
g = sns.FacetGrid(df, col='RUN', row='SCENARIO', hue='DAY', height=5)
g = g.map(sns.distplot, 'MEAN', bins=range(20, 51, 5), kde=False, hist_kws=dict(edgecolor="k", linewidth=1)).add_legend()
plt.show()
Plot with kde=True
g = sns.FacetGrid(df, col='RUN', row='SCENARIO', hue='DAY', height=5, palette='GnBu')
g = g.map(sns.distplot, 'MEAN', bins=range(20, 51, 5), kde=True, hist_kws=dict(edgecolor="k", linewidth=1)).add_legend()
plt.show()
Plots with error bars
Using how to add error bars to histogram diagram in python
Using df from above
Use matplotlib.pyplot.errorbar to plot the error bars on the histogram.
from itertools import product
# create unique combinations for filtering df
scenarios = df.SCENARIO.unique()
runs = df.RUN.unique()
days = df.DAY.unique()
combo_list = [scenarios, runs, days]
results = list(product(*combo_list))
# plot
for i, result in enumerate(results, 1): # iterate through each set of combinations
s, r, d = result
data = df[(df.SCENARIO == s) & (df.RUN == r) & (df.DAY == d)] # filter dataframe
# add subplot rows, columns; needs to equal the number of combinations in results
plt.subplot(2, 4, i)
# plot hist and unpack values
n, bins, _ = plt.hist(x='MEAN', bins=range(20, 51, 5), data=data, color='g')
# calculate bin centers
bin_centers = 0.5 * (bins[:-1] + bins[1:])
# draw errobars, use the sqrt error. You can use what you want there
# poissonian 1 sigma intervals would make more sense
plt.errorbar(bin_centers, n, yerr=np.sqrt(n), fmt='k.')
plt.title(f'Scenario: {s} | Run: {r} | Day: {d}')
plt.tight_layout()
plt.show()

How to get two legends using pandas plot, one for the colors of the stacked bars and one for the hatches of the bars?

I have been trying to understand the answer of this post in order to populate two different legends.
I create a clustered stacked bar plot with different hatches for each bar and my code below is a bit different from the answer of the aforementioned post.
But I have not been able to figure out how to get one legend with the colors and one legend with the hatches.
The color legend should correspond to A, B, C, D, E and the hatch legend should indicate "with" if bar is hatched and "without" if non-hatched.
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap as coloring
# copy the dfs below and use pd.read_clipboard() to reproduce
df_1
A B C D E
Mg 10 15 23 25 27
Ca 30 33 0 20 17
df_2
A B C D E
Mg 20 12 8 40 10
Ca 7 26 12 22 16
hatches=(' ', '//')
colors_ABCDE=['tomato', 'gold', 'greenyellow', 'forestgreen', 'palevioletred']
dfs=[df_1,df_2]
for each_df, df in enumerate(dfs):
df.plot(ax=plt.subplot(111), kind="barh", \
stacked=True, hatch=hatches[each_df], \
colormap=coloring.from_list("my_colormap", colors_ABCDE), \
figsize=(7,2.5), position=len(dfs)-each_df-1, \
align='center', width=0.2, edgecolor="darkgrey")
plt.legend(loc='center left', bbox_to_anchor=(1.0, 0.5), fontsize=12)
The plot I manage to get is:
Any ideas how to create two legends and place them one next to the other or one below the other? Thanks in advance ^_^
Since adding legends in matplotlib is a complex, extensive step, consider using the very link you cite with function solution by #jrjc. However, you will need to adjust function to your horizontal bar graph needs. Specifically:
Add an argument for color map and in DataFrame.plot call
Adjust bar plot from kind='bar' to kind='barh' for horizontal version
Swap x for y in line: rect.set_y(rect.get_y() + 1 / float(n_df + 1) * i / float(n_col))
Swap width for height in line: rect.set_height(1 / float(n_df + 1))
Adjust axe.set_xticks and axe.set_xticklabels for np.arange(0, 120, 20) values
Function
import numpy as np
import pandas as pd
import matplotlib.cm as cm
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap as coloring
def plot_clustered_stacked(dfall, labels=None, title="multiple stacked bar plot", H="//",
colors_ABCDE=['tomato', 'gold', 'greenyellow', 'forestgreen', 'palevioletred'], **kwargs):
"""
CREDIT: #jrjc (https://stackoverflow.com/a/22845857/1422451)
Given a list of dataframes, with identical columns and index, create a clustered stacked bar plot.
labels is a list of the names of the dataframe, used for the legend
title is a string for the title of the plot
H is the hatch used for identification of the different dataframe
"""
n_df = len(dfall)
n_col = len(dfall[0].columns)
n_ind = len(dfall[0].index)
axe = plt.subplot(111)
for df in dfall : # for each data frame
axe = df.plot(kind="barh",
linewidth=0,
stacked=True,
ax=axe,
legend=False,
grid=False,
colormap=coloring.from_list("my_colormap", colors_ABCDE),
edgecolor="darkgrey",
**kwargs) # make bar plots
h,l = axe.get_legend_handles_labels() # get the handles we want to modify
for i in range(0, n_df * n_col, n_col): # len(h) = n_col * n_df
for j, pa in enumerate(h[i:i+n_col]):
for rect in pa.patches: # for each index
rect.set_y(rect.get_y() + 1 / float(n_df + 2) * i / float(n_col))
rect.set_hatch(H * int(i / n_col)) #edited part
rect.set_height(1 / float(n_df + 2))
axe.set_xticks(np.arange(0, 125, 20))
axe.set_xticklabels(np.arange(0, 125, 20).tolist(), rotation = 0)
axe.margins(x=0, tight=None)
axe.set_title(title)
# Add invisible data to add another legend
n=[]
for i in range(n_df):
n.append(axe.bar(0, 0, color="gray", hatch=H * i, edgecolor="darkgrey"))
l1 = axe.legend(h[:n_col], l[:n_col], loc=[1.01, 0.5])
if labels is not None:
l2 = plt.legend(n, labels, loc=[1.01, 0.1])
axe.add_artist(l1)
return axe
Call
plt.figure(figsize=(10, 4))
plot_clustered_stacked([df_1, df_2],["df_1", "df_2"])
plt.show()
plt.clf()
plt.close()
Output
I thought that this function solution by #jrjc is rather perplexing for my understanding and thus, I preferred to alter my own thing a little and adjust it.
So, it took my some time to understand that when a second legend is created for a plot, python automatically erases the first one and this is when add_artist() must be employed.
The other prerequisite in order to add the second legend is to name the plot and apply the .add_artist() method to that specific plot, so that python knows where to stick that new piece.
In short, this is how I managed to create the plot I had in mind and I hope that the comments will make it somehow clearer and useful for anyone.
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap as coloring
import matplotlib.patches as mpatches
# copy the dfs below and use pd.read_clipboard() to reproduce
df_1
A B C D E
Mg 10 15 23 25 27
Ca 30 33 0 20 17
df_2
A B C D E
Mg 20 12 8 40 10
Ca 7 26 12 22 16
hatches=(' ', '//')
colors_ABCDE=['tomato', 'gold', 'greenyellow', 'forestgreen', 'palevioletred']
dfs=[df_1,df_2]
for each_df, df in enumerate(dfs):
#I name the plot as "figure"
figure=df.plot(ax=plt.subplot(111), kind="barh", \
stacked=True, hatch=hatches[each_df], \
colormap=coloring.from_list("my_colormap", colors_ABCDE), \
figsize=(7,2.5), position=len(dfs)-each_df-1, \
align='center', width=0.2, edgecolor="darkgrey", \
legend=False) #I had to False the legend too
legend_1=plt.legend(df_1.columns, loc='center left', bbox_to_anchor=(1.0, 0.5), fontsize=12)
patch_hatched = mpatches.Patch(facecolor='beige', hatch='///', edgecolor="darkgrey", label='hatched')
patch_unhatched = mpatches.Patch(facecolor='beige', hatch=' ', edgecolor="darkgrey", label='non-hatched')
legend_2=plt.legend(handles=[patch_hatched, patch_unhatched], loc='center left', bbox_to_anchor=(1.15, 0.5), fontsize=12)
# as soon as a second legend is made, the first disappears and needs to be added back again
figure.add_artist(legend_1) #python now knows that "figure" must take the "legend_1" along with "legend_2"
I am pretty sure that it can be even more elegant and automated.

Dynamically add subplots in matplotlib with more than one column

How can I dynamically add a new plot to bunch of subplots if I'm using more than one column to display my subplots? This answers this question for one column, but I cant seem to modify the answers there to make it dynamically add to a subplot with x columns
I modified Sadarthrion's answer and attempted the following. Here, for sake of an example, I made number_of_subplots=11 and num_cols = 3.
import matplotlib.pyplot as plt
def plotSubplots(number_of_subplots,num_cols):
# Start with one
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot([1,2,3])
for j in range(number_of_subplots):
if j > 0:
# Now later you get a new subplot; change the geometry of the existing
n = len(fig.axes)
for i in range(n):
fig.axes[i].change_geometry(n+1, num_cols, i+1)
# Add the new
ax = fig.add_subplot(n+1, 1, n+1)
ax.plot([4,5,6])
plt.show()
plotSubplots(11,3)
As you can see this isn't giving me what I want. The first plot takes up all the columns and the additional plots are smaller than they should be
EDIT:
('2.7.6 | 64-bit | (default, Sep 15 2014, 17:36:35) [MSC v.1500 64 bit (AMD64)]'
Also I have matplotlib version 1.4.3:
import matplotlib as mpl
print mpl.__version__
1.4.3
I tried Paul's answer below and get the following error message:
import math
import matplotlib.pyplot as plt
from matplotlib import gridspec
def do_plot(ax):
ax.plot([1,2,3], [4,5,6], 'k.')
N = 11
cols = 3
rows = math.ceil(N / cols)
gs = gridspec.GridSpec(rows, cols)
fig = plt.figure()
for n in range(N):
ax = fig.add_subplot(gs[n])
do_plot(ax)
fig.tight_layout()
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-1-f74203b1c1bf> in <module>()
15 fig = plt.figure()
16 for n in range(N):
---> 17 ax = fig.add_subplot(gs[n])
18 do_plot(ax)
19
C:\Users\user\AppData\Local\Enthought\Canopy\User\lib\site-packages\matplotlib\figure.pyc in add_subplot(self, *args, **kwargs)
962 self._axstack.remove(ax)
963
--> 964 a = subplot_class_factory(projection_class)(self, *args, **kwargs)
965
966 self._axstack.add(key, a)
C:\Users\user\AppData\Local\Enthought\Canopy\User\lib\site-packages\matplotlib\axes\_subplots.pyc in __init__(self, fig, *args, **kwargs)
73 raise ValueError('Illegal argument(s) to subplot: %s' % (args,))
74
---> 75 self.update_params()
76
77 # _axes_class is set in the subplot_class_factory
C:\Users\user\AppData\Local\Enthought\Canopy\User\lib\site-packages\matplotlib\axes\_subplots.pyc in update_params(self)
113 self.figbox, self.rowNum, self.colNum, self.numRows, self.numCols = 114 self.get_subplotspec().get_position(self.figure,
--> 115 return_all=True)
116
117 def is_first_col(self):
C:\Users\user\AppData\Local\Enthought\Canopy\User\lib\site-packages\matplotlib\gridspec.pyc in get_position(self, fig, return_all)
423
424 figBottoms, figTops, figLefts, figRights = --> 425 gridspec.get_grid_positions(fig)
426
427
C:\Users\user\AppData\Local\Enthought\Canopy\User\lib\site-packages\matplotlib\gridspec.pyc in get_grid_positions(self, fig)
103 cellHeights = [netHeight*r/tr for r in self._row_height_ratios]
104 else:
--> 105 cellHeights = [cellH] * nrows
106
107 sepHeights = [0] + ([sepH] * (nrows-1))
TypeError: can't multiply sequence by non-int of type 'float'
Assuming at least 1 dimension of your grid and the total number of plots is known, I would use the gridspec module and a little bit of math.
import math
import matplotlib.pyplot as plt
from matplotlib import gridspec
def do_plot(ax):
ax.plot([1,2,3], [4,5,6], 'k.')
N = 11
cols = 3
rows = int(math.ceil(N / cols))
gs = gridspec.GridSpec(rows, cols)
fig = plt.figure()
for n in range(N):
ax = fig.add_subplot(gs[n])
do_plot(ax)
fig.tight_layout()
Here's the solution I ended up with. It lets you reference subplots by name, and adds a new subplot if that name has not been used yet, repositioning all previous subplots in the process.
Usage:
set_named_subplot('plot-a') # Create a new plot
plt.plot(np.sin(np.linspace(0, 10, 100))) # Plot a curve
set_named_subplot('plot-b') # Create a new plot
plt.imshow(np.random.randn(10, 10)) # Draw image
set_named_subplot('plot-a') # Set the first plot as the current one
plt.plot(np.cos(np.linspace(0, 10, 100))) # Plot another curve in the first plot
plt.show() # Will show two plots
The code:
import matplotlib.pyplot as plt
import numpy as np
def add_subplot(fig = None, layout = 'grid'):
"""
Add a subplot, and adjust the positions of the other subplots appropriately.
Lifted from this answer: http://stackoverflow.com/a/29962074/851699
:param fig: The figure, or None to select current figure
:param layout: 'h' for horizontal layout, 'v' for vertical layout, 'g' for approximately-square grid
:return: A new axes object
"""
if fig is None:
fig = plt.gcf()
n = len(fig.axes)
n_rows, n_cols = (1, n+1) if layout in ('h', 'horizontal') else (n+1, 1) if layout in ('v', 'vertical') else \
vector_length_to_tile_dims(n+1) if layout in ('g', 'grid') else bad_value(layout)
for i in range(n):
fig.axes[i].change_geometry(n_rows, n_cols, i+1)
ax = fig.add_subplot(n_rows, n_cols, n+1)
return ax
_subplots = {}
def set_named_subplot(name, fig=None, layout='grid'):
"""
Set the current axes. If "name" has been defined, just return that axes, otherwise make a new one.
:param name: The name of the subplot
:param fig: The figure, or None to select current figure
:param layout: 'h' for horizontal layout, 'v' for vertical layout, 'g' for approximately-square grid
:return: An axes object
"""
if name in _subplots:
plt.subplot(_subplots[name])
else:
_subplots[name] = add_subplot(fig=fig, layout=layout)
return _subplots[name]
def vector_length_to_tile_dims(vector_length):
"""
You have vector_length tiles to put in a 2-D grid. Find the size
of the grid that best matches the desired aspect ratio.
TODO: Actually do this with aspect ratio
:param vector_length:
:param desired_aspect_ratio:
:return: n_rows, n_cols
"""
n_cols = np.ceil(np.sqrt(vector_length))
n_rows = np.ceil(vector_length/n_cols)
grid_shape = int(n_rows), int(n_cols)
return grid_shape
def bad_value(value, explanation = None):
"""
:param value: Raise ValueError. Useful when doing conditional assignment.
e.g.
dutch_hand = 'links' if eng_hand=='left' else 'rechts' if eng_hand=='right' else bad_value(eng_hand)
"""
raise ValueError('Bad Value: %s%s' % (value, ': '+explanation if explanation is not None else ''))
from math import ceil
from PyQt5 import QtWidgets, QtCore
from matplotlib.gridspec import GridSpec
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
class MplCanvas(FigureCanvas):
"""
Frontend class. This is the FigureCanvas as well as plotting functionality.
Plotting use pyqt5.
"""
def __init__(self, parent=None):
self.figure = Figure()
gs = GridSpec(1,1)
self.figure.add_subplot(gs[0])
self.axes = self.figure.axes
super().__init__(self.figure)
self.canvas = self.figure.canvas
self.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
self.updateGeometry()
self.setParent(parent)
def add(self, cols=2):
N = len(self.axes) + 1
rows = int(ceil(N / cols))
grid = GridSpec(rows, cols)
for gs, ax in zip(grid, self.axes):
ax.set_position(gs.get_position(self.figure))
self.figure.add_subplot(grid[N-1])
self.axes = self.figure.axes
self.canvas.draw()
Was doing some PyQt5 work, but the add method shows how to dynamically add a new subplot. The set_position method of Axes is used to change the old position to new position. Then you add a new subplot with the new position.
I wrote a function that automatically formats all of the subplots into a compact, square-like shape.
To go off of Paul H's answer, we can use gridspec to dynamically add subplots to a figure. However, I then made an improvement. My code automatically arranges the subplots so that the entire figure will be compact and square-like. This ensures that there will be roughly the same amount of rows and columns in the subplot.
The number of columns equals the square root of n_plots rounded down and then enough rows are created so there will be enough spots for all of the subplots.
Check it out:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import gridspec
def arrange_subplots(xs, ys, n_plots):
"""
---- Parameters ----
xs (n_plots, d): list with n_plot different lists of x values that we wish to plot
ys (n_plots, d): list with n_plot different lists of y values that we wish to plot
n_plots (int): the number of desired subplots
"""
# compute the number of rows and columns
n_cols = int(np.sqrt(n_plots))
n_rows = int(np.ceil(n_plots / n_cols))
# setup the plot
gs = gridspec.GridSpec(n_rows, n_cols)
scale = max(n_cols, n_rows)
fig = plt.figure(figsize=(5 * scale, 5 * scale))
# loop through each subplot and plot values there
for i in range(n_plots):
ax = fig.add_subplot(gs[i])
ax.plot(xs[i], ys[i])
Here are a couple of example images to compare
n_plots = 5
n_plots = 6
n_plots = 10
n_plots = 15

Categories

Resources