I want to create a plot consisting of several subplots with shared x/y axes.
It should look something like this from the documentation (though my subplots will be scatterblots): (code here)
But I want to create the subplots dynamically!
So the number of subplots depends on the output of a previous function. (It will probably be around 3 to 15 subplots per diagram, each from a distinct dataset, depending on the input of my script.)
Can anyone tell me how to accomplish that?
Suppose you know total subplots and total columns you want to use:
import matplotlib.pyplot as plt
# Subplots are organized in a Rows x Cols Grid
# Tot and Cols are known
Tot = number_of_subplots
Cols = number_of_columns
# Compute Rows required
Rows = Tot // Cols
# EDIT for correct number of rows:
# If one additional row is necessary -> add one:
if Tot % Cols != 0:
Rows += 1
# Create a Position index
Position = range(1,Tot + 1)
First instance of Rows accounts only for rows completely filled by subplots, then is added one more Row if 1 or 2 or ... Cols - 1 subplots still need location.
Then create figure and add subplots with a for loop.
# Create main figure
fig = plt.figure(1)
for k in range(Tot):
# add every single subplot to the figure with a for loop
ax = fig.add_subplot(Rows,Cols,Position[k])
ax.plot(x,y) # Or whatever you want in the subplot
plt.show()
Please note that you need the range Position to move the subplots into the right place.
import matplotlib.pyplot as plt
from pylab import *
import numpy as np
x = np.linspace(0, 2*np.pi, 400)
y = np.sin(x**2)
subplots_adjust(hspace=0.000)
number_of_subplots=3
for i,v in enumerate(xrange(number_of_subplots)):
v = v+1
ax1 = subplot(number_of_subplots,1,v)
ax1.plot(x,y)
plt.show()
This code works but you will need to correct the axes. I used to subplot to plot 3 graphs all in the same column. All you need to do is assign an integer to number_of_plots variable. If the X and Y values are different for each plot you will need to assign them for each plot.
subplot works as follows, if for example I had a subplot values of 3,1,1. This creates a 3x1 grid and places the plot in the 1st position. In the next interation if my subplot values were 3,1,2 it again creates a 3x1 grid but places the plot in the 2nd position and so forth.
Based on this post, what you want to do is something like this:
import matplotlib.pyplot as plt
# Start with one
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot([1,2,3])
# 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, 1, i+1)
# Add the new
ax = fig.add_subplot(n+1, 1, n+1)
ax.plot([4,5,6])
plt.show()
However, Paul H's answer points to the submodule called gridspec which might make the above easier. I am leaving that as an exercise for the reader ^_~.
Instead of counting your own number of rows and columns, I found it easier to create the subplots using plt.subplots first, then iterate through the axes object to add plots.
import matplotlib.pyplot as plt
import numpy as np
fig, axes = plt.subplots(nrows=3, ncols=2, figsize=(12, 8))
x_array = np.random.randn(6, 10)
y_array = np.random.randn(6, 10)
i = 0
for row in axes:
for ax in row:
x = x_array[i]
y = y_array[i]
ax.scatter(x, y)
ax.set_title("Plot " + str(i))
i += 1
plt.tight_layout()
plt.show()
Here I use i to iterate through elements of x_array and y_array, but you can likewise easily iterate through functions, or columns of dataframes to dynamically generate graphs.
Related
Currently my chart is showing only the main big chart on the left.
However, I now want to add the two smaller plots to the right-hand side of my main plot; with each individual set of data.
I am struggling with subplots to figure out how to do this. My photo below shows my desired output.
filenamesK = glob("C:/Users/Ke*.csv")
filenamesZ = glob("C:/Users/Ze*.csv")
K_Z_Averages = {'K':[], 'Z':[]}
# We will create a function for plotting, instead of nesting lots of if statements within a long for-loop.
def plot_data(filename, fig_ax, color):
df = pd.read_csv(f, sep=',',skiprows=24) # Read in the csv.
df.columns=['sample','Time','ms','Temp1'] # Set the column names
df=df.astype(str) # Set the data type as a string.
df["Temp1"] = df["Temp1"].str.replace('\+ ', '').str.replace(' ', '').astype(float) # Convert to float
# Take the average of the data from the Temp1 column, starting from sample 60 until sample 150.
avg_Temp1 = df.iloc[60-1:150+1]["Temp1"].mean()
# Append this average to a K_Z_Averages, containing a column for average from each K file and the average from each Z file.
# Glob returns the whole path, so you need to replace 0 for 10.
K_Z_Averages[os.path.basename(filename)[0]].append(avg_Temp1)
fig_ax.plot(df[["Temp1"]], color=color)
fig, ax = plt.subplots(figsize=(20, 15))
for f in filenamesK:
plot_data(f, ax, 'blue')
for f in filenamesZ:
plot_data(f, ax, 'red')
plt.show()
#max 's answer is fine, but something you can also do matplotlib>=3.3 is
import matplotlib.pyplot as plt
fig = plt.figure(constrained_layout=True)
axs = fig.subplot_mosaic([['Left', 'TopRight'],['Left', 'BottomRight']],
gridspec_kw={'width_ratios':[2, 1]})
axs['Left'].set_title('Plot on Left')
axs['TopRight'].set_title('Plot Top Right')
axs['BottomRight'].set_title('Plot Bottom Right')
Note hw the repeated name 'Left' is used twice to indicate that this subplot takes up two slots in the layout. Also note the use of width_ratios.
This is a tricky question. Essentially, you can place a grid on a figure (add_gridspec()) and than open subplots (add_subplot()) in and over different grid elements.
import matplotlib.pyplot as plt
# open figure
fig = plt.figure()
# add grid specifications
gs = fig.add_gridspec(2, 3)
# open axes/subplots
axs = []
axs.append( fig.add_subplot(gs[:,0:2]) ) # large subplot (2 rows, 2 columns)
axs.append( fig.add_subplot(gs[0,2]) ) # small subplot (1st row, 3rd column)
axs.append( fig.add_subplot(gs[1,2]) ) # small subplot (2nd row, 3rd column)
say I was testing a range of parameters of a clustering algorithm and I wanted to write python code that would plot all the results of the algorithm in subplots 2 to a row
is there a way to do this without pre-calculating how many total plots you would need?
something like:
for c in range(3,10):
k = KMeans(n_clusters=c)
plt.subplots(_, 2, _)
plt.scatter(data=data, x='x', y='y', c=k.fit_predict(data))
... and then it would just plot 'data' with 'c' clusters 2 plots per row until it ran out of stuff to plot.
thanks!
This answer from the question Dynamically add/create subplots in matplotlib explains a way to do it:
https://stackoverflow.com/a/29962074/3827277
verbatim copy & paste:
import matplotlib.pyplot as plt
# Start with one
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot([1,2,3])
# 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, 1, i+1)
# Add the new
ax = fig.add_subplot(n+1, 1, n+1)
ax.plot([4,5,6])
plt.show()
Here is an example of a plot I am generating using Pandas and MatPlotLib.
Please note that even though I stated sharey = True in the code, the y-Axis is only shared across each row.This isn't much help to me, as I need to compare all plots against each other.
How can I use just one axis for the entire plot? I'd also ideally want that axis repeated for each plot.
Thank you!
for field in chosenFields:
for dataID in dataIDs:
fig = plt.figure()
subplots = [fig.add_subplot(rows, cols, subplot) for
subplot in range(1, len(fileNames) + 1)]
for subplot, plot, fileName in zip(subplots, plots, fileNames):
graphData = Build_Graphs.prepareOutputGraph(plot[0],
field,
dataID,
batchName,
segmentName)
haveLegend = True if len(graphData.columns) < 12 else False
subplt = graphData.plot(ax = subplot,
legend = haveLegend,
title = fileName,
sharey = True)
Build_Graphs.labelGraph(subplt, field, dataID, batchName, segmentName)
plt.get_current_fig_manager().window.showMaximized()
writeOutput(outputDirectory, field, dataID, graphData)
plt.show()
In order to get the same axis range repeated for each plot, you can get_ylim from all existing and use global min/max to set all the axes,
import numpy as np
import matplotlib.pyplot as plt
#Setup dummy data
fig, subplots = plt.subplots(2,3)
x = np.linspace(0,2.*np.pi,1000)
[sp.plot(x,np.sin(x)*(10*np.random.randn(1))) for sp in subplots.reshape(-1)]
#Get global minimum and maximum y values accross all axis
ygmin = 0.; ygmax = 0.
for sp in subplots.reshape(-1):
ymin, ymax = sp.get_ylim()
ygmin = min(ygmin,ymin)
ygmax = max(ygmax,ymax)
#Set same axis for all subplots
[sp.set_ylim((ygmin,ygmax)) for sp in subplots.reshape(-1)]
plt.show()
As suggested by paulH, this can also be done with sharey=True as part of plt.subplots. However, the y axis is hidden for anything but the first axis by default, so you need to tell matplotlib to show these again,
import numpy as np
import matplotlib.pyplot as plt
#Setup dummy data
fig, subplots = plt.subplots(2,3,sharey=True)
x = np.linspace(0,2.*np.pi,1000)
[sp.plot(x,np.sin(x)*(10*np.random.randn(1))) for sp in subplots.reshape(-1)]
#Show axis on all subplots
[plt.setp(sp.get_yticklabels(), visible=True) for sp in subplots.reshape(-1)]
plt.show()
You can also specify sharey="col" or sharey="row" to share axes alone the column or row respectively.
Is it possible to embed a changing number of plots in a matplotlib axis? For example, the inset_axes method is used to place inset axes inside parent axes:
However, I have several rows of plots and I want to include some inset axes inside the last axis object of each row.
fig, ax = plt.subplots(2,4, figsize=(15,15))
for i in range(2):
ax[i][0].plot(np.random.random(40))
ax[i][2].plot(np.random.random(40))
ax[i][3].plot(np.random.random(40))
# number of inset axes
number_inset = 5
for j in range(number_inset):
ax[i][4].plot(np.random.random(40))
Here instead of the 5 plots drawn in the last column, I want several inset axes containing a plot. Something like this:
The reason for this is that every row refers to a different item to be plotted and the last column is supposed to contain the components of such item. Is there a way to do this in matplotlib or maybe an alternative way to visualize this?
Thanks
As #hitzg mentioned, the most common way to accomplish something like this is to use GridSpec. GridSpec creates an imaginary grid object that you can slice to produce subplots. It's an easy way to align fairly complex layouts that you want to follow a regular grid.
However, it may not be immediately obvious how to use it in this case. You'll need to create a GridSpec with numrows * numinsets rows by numcols columns and then create the "main" axes by slicing it with intervals of numinsets.
In the example below (2 rows, 4 columns, 3 insets), we'd slice by gs[:3, 0] to get the upper left "main" axes, gs[3:, 0] to get the lower left "main" axes, gs[:3, 1] to get the next upper axes, etc. For the insets, each one is gs[i, -1].
As a complete example:
import numpy as np
import matplotlib.pyplot as plt
def build_axes_with_insets(numrows, numcols, numinsets, **kwargs):
"""
Makes a *numrows* x *numcols* grid of subplots with *numinsets* subplots
embedded as "sub-rows" in the last column of each row.
Returns a figure object and a *numrows* x *numcols* object ndarray where
all but the last column consists of axes objects, and the last column is a
*numinsets* length object ndarray of axes objects.
"""
fig = plt.figure(**kwargs)
gs = plt.GridSpec(numrows*numinsets, numcols)
axes = np.empty([numrows, numcols], dtype=object)
for i in range(numrows):
# Add "main" axes...
for j in range(numcols - 1):
axes[i, j] = fig.add_subplot(gs[i*numinsets:(i+1)*numinsets, j])
# Add inset axes...
for k in range(numinsets):
m = k + i * numinsets
axes[i, -1][k] = fig.add_subplot(gs[m, -1])
return fig, axes
def plot(axes):
"""Recursive plotting function just to put something on each axes."""
for ax in axes.flat:
data = np.random.normal(0, 1, 100).cumsum()
try:
ax.plot(data)
ax.set(xticklabels=[], yticklabels=[])
except AttributeError:
plot(ax)
fig, axes = build_axes_with_insets(2, 4, 3, figsize=(12, 6))
plot(axes)
fig.tight_layout()
plt.show()
This is what I did to obtain the same result without setting the number of inset plots in advance.
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import numpy as np
fig = plt.figure(figsize=(12,6))
nrows = 2
ncols = 4
# changing the shape of GridSpec's output
outer_grid = gridspec.GridSpec(nrows, ncols)
grid = []
for i in range(nrows*ncols):
grid.append(outer_grid[i])
outer_grid = np.array(grid).reshape(nrows,ncols)
for i in range(nrows):
inner_grid_1 = gridspec.GridSpecFromSubplotSpec(1, 1,
subplot_spec=outer_grid[i][0])
ax = plt.Subplot(fig, inner_grid_1[0])
ax.plot(np.random.normal(0,1,50).cumsum())
fig.add_subplot(ax)
inner_grid_2 = gridspec.GridSpecFromSubplotSpec(1, 1,
subplot_spec=outer_grid[i][1])
ax2 = plt.Subplot(fig, inner_grid_2[0])
ax2.plot(np.random.normal(0,1,50).cumsum())
fig.add_subplot(ax2)
inner_grid_3 = gridspec.GridSpecFromSubplotSpec(1, 1,
subplot_spec=outer_grid[i][2])
ax3 = plt.Subplot(fig, inner_grid_3[0])
ax3.plot(np.random.normal(0,1,50).cumsum())
fig.add_subplot(ax3)
# this value can be set based on some other calculation depending
# on each row
numinsets = 3
inner_grid_4 = gridspec.GridSpecFromSubplotSpec(numinsets, 1,
subplot_spec=outer_grid[i][3])
# Adding subplots to the last inner grid
for j in range(inner_grid_4.get_geometry()[0]):
ax4 = plt.Subplot(fig, inner_grid_4[j])
ax4.plot(np.random.normal(0,1,50).cumsum())
fig.add_subplot(ax4)
# Removing labels
for ax in fig.axes:
ax.set(xticklabels=[], yticklabels=[])
fig.tight_layout()
I am trying to make a matplotlib figure that will have multiple horizontal boxplots stacked on one another. The documentation shows both how to make a single horizontal boxplot and how to make multiple vertically oriented plots in this section.
I tried using subplots as in the following code:
import numpy as np
import pylab as plt
totfigs = 5
plt.figure()
plt.hold = True
for i in np.arange(totfigs):
x = np.random.random(50)
plt.subplot('{0}{1}{2}'.format(totfigs,1,i+1))
plt.boxplot(x,vert=0)
plt.show()
My output results in just a single horizontal boxplot though.
Any suggestions anyone?
Edit: Thanks to #joaquin, I fixed the plt.subplot call line. Now the subplot version works, but still would like the boxplots all in one figure...
If I'm understanding you correctly, you just need to pass boxplot a list (or a 2d array) containing each array you want to plot.
import numpy as np
import pylab as plt
totfigs = 5
plt.figure()
plt.hold = True
boxes=[]
for i in np.arange(totfigs):
x = np.random.random(50)
boxes.append(x)
plt.boxplot(boxes,vert=0)
plt.show()
try:
plt.subplot('{0}{1}{2}'.format(totfigs, 1, i+1) # n rows, 1 column
or
plt.subplot('{0}{1}{2}'.format(1, totfigs, i+1)) # 1 row, n columns
from the docstring:
subplot(*args, **kwargs)
Create a subplot command, creating axes with::
subplot(numRows, numCols, plotNum)
where plotNum = 1 is the first plot number and increasing plotNums
fill rows first. max(plotNum) == numRows * numCols
if you want them all together, shift them conveniently. As an example with a constant shift:
for i in np.arange(totfigs):
x = np.random.random(50)
plt.boxplot(x+(i*2),vert=0)