How to plot figures in subplots (Matplotlib) - python

I understand there are various ways to plot multiple graphs in one figure. One such way is using axes, e.g.
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
ax.plot([range(8)])
ax.plot(...)
Since I have a function that beautifies my graphs and subsequently returns a figure, I would like to use that figure to be plotted in my subplots. It should look similar to this:
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
ax.plot(figure1) # where figure is a plt.figure object
ax.plot(figure2)
This does not work but how can I make it work? Is there a way to put figures inside subplots or a workaround to plot multiple figures in one overall figure?
Any help on this is much appreciated.
Thanks in advance for your comments.

If the goal is just to customize individual subplots, why not change your function to change the current figure on the fly rather than return a figure. From matplotlib and seaborn, can you just change the plot settings as they are being plotted?
import numpy as np
import matplotlib.pyplot as plt
plt.figure()
x1 = np.linspace(0.0, 5.0)
x2 = np.linspace(0.0, 2.0)
y1 = np.cos(2 * np.pi * x1) * np.exp(-x1)
y2 = np.cos(2 * np.pi * x2)
plt.subplot(2, 1, 1)
plt.plot(x1, y1, 'ko-')
plt.title('A tale of 2 subplots')
plt.ylabel('Damped oscillation')
import seaborn as sns
plt.subplot(2, 1, 2)
plt.plot(x2, y2, 'r.-')
plt.xlabel('time (s)')
plt.ylabel('Undamped')
plt.show()
Perhaps I don't understand your question entirely. Is this 'beautification' function complex?...

A possible solution is
import matplotlib.pyplot as plt
# Create two subplots horizontally aligned (one row, two columns)
fig, ax = plt.subplots(1,2)
# Note that ax is now an array consisting of the individual axis
ax[0].plot(data1)
ax[1].plot(data2)
However, in order to work data1,2 needs to be data. If you have a function which already plots the data for you I would recommend to include an axis argument to your function. For example
def my_plot(data,ax=None):
if ax == None:
# your previous code
else:
# your modified code which plots directly to the axis
# for example: ax.plot(data)
Then you can plot it like
import matplotlib.pyplot as plt
# Create two subplots horizontally aligned
fig, ax = plt.subplots(2)
# Note that ax is now an array consisting of the individual axis
my_plot(data1,ax=ax[0])
my_plot(data2,ax=ax[1])

Related

Why doesn't the show() function in matplotlib.pyplot work more than once for the same Axes object?

Out of pure curiosity, I would love to know why the final plt.show() does not display both plots on ax. Only the first plt.show() seems to do anything, because only the plot of y = sin(x) shows up. Here is the code sample:
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots()
x = np.linspace(1, 100, 10)
ax.plot(x, np.sin(x))
plt.show()
ax.plot(x, x)
plt.show()
Appreciate any help on this, because it bugs me to not understand why this is the case, even after a lot of searches. PS: I know that the code is useless and dumb, but I would still like to know for future use.
Your code
## load libraries
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots()
x = np.linspace(1, 100, 10)
## assign first plot
ax.plot(x, np.sin(x))
#plt.show()
## assign second plot
ax.plot(x, x)
## render the plots
plt.show()
One reason why plt.show() didn't 'show' more than once.
You are using subplots.
Your plots are on the same axes
plt.show() display all open figures. Your ax.plot(x, np.sin(x)) will be shown and the figure closed. The second is on the same ax and will not be shown anymore.
Documentation: matplotlib.pyplot.show()
[Alternate]
If however you call plt.plot() separately, (without subplots axes), you would get two plots; each with its own dimensions.
PS: below works in Jupyter (mybinder)
## load libraries
import matplotlib.pyplot as plt1
import numpy as np
x = np.linspace(1, 100, 10)
## first plot
plt1.plot(x, np.sin(x))
## render first plot
plt1.show()
Followed by
## second plot
plt1.plot(x, x)
## render the plot
plt1.show()
Clarity from the documentation: matplotlib.pyplot is a state-based interface to matplotlib
[Updated]
#JohanC lumping up the explanatory code and the alternate code.
The explanation 1, 2, and 3 remains.
The alternate code remain. or
OP can put the two plots on their own ax, and have plt.show each.
I didn't intend including this before. However, for completeness:
## load libraries
import matplotlib.pyplot as plt
import numpy as np
## tuple of desired two axes
## unpack AxesSubplot on two rows
fig, (ax1, ax2), = plt.subplots(nrows=2)
## assign variable
x = np.linspace(1, 100, 10)
## assign first plot
ax1.plot(x, np.sin(x))
#plt.show()
## assign second plot
ax2.plot(x, x)
## render the plots
## Rendering might not be 'smooth' in Jupyter
plt.show()

How to change length of one plot in subplot?

How to change the length of one plot in a subplot?
It may be a simple problem but I have difficulty solving this.
To represent the result of signal analysis, I represented three plots in a subplot.
But, because the third graph had a colorbar, only this is short.
How can I solve this problem?
I added some parts that draw each plot in a subplot in my code except detail.
To avoid misunderstanding, I added figure.
In the below figure, the length of the spectrogram plot in the python figure(left) is shorter than the above two plots. But the length of the spectrogram plot in the Matlab figure(right) is equal to the above plots. How can make the length of the third plot be equal with the above plots, like the result of Matlab?
import matplotlib.pyplot as plt
fig, (ax1, ax2, ax3, cbar) = plt.subplots(3, 2)
ax1.plot(sb['Seconds'], sb['Real'], 'dodgerblue', linewidth = 0.5)
ax2.plot(f2, np.log(P3), 'k', linewidth = 0.5)
s, freqs, bins, im = ax3.specgram(y, NFFT = N, Fs=Fs1, cmap='jet')
cbar = plt.colorbar(im, ax=ax3, orientation = 'vertical', pad = 0.009)
If you already have the figure object use:
f.set_figheight(15)
f.set_figwidth(15)
But if you use the .subplots() command (as in the examples you're showing) to create a new figure you can also use:
f, axs = plt.subplots(2,2,figsize=(15,15))
For example: -
Alternatively, create a figure() object using the figsize argument and then use add_subplot to add your subplots. E.g.
import matplotlib.pyplot as plt
import numpy as np
f = plt.figure(figsize=(10,3))
ax = f.add_subplot(121)
ax2 = f.add_subplot(122)
x = np.linspace(0,4,1000)
ax.plot(x, np.sin(x))
ax2.plot(x, np.cos(x), 'r:')
Benefits of this method are that the syntax is closer to calls of subplot() instead of subplots(). E.g. subplots doesn't seem to support using a GridSpec for controlling the spacing of the subplots, but both subplot() and add_subplot() do.

Histogram at specific coordinates inside axes

What I want to achieve with Python 3.6 is something like this :
Obviously made in paint and missing some ticks on the xAxis. Is something like this possible? Essentially, can I control exactly where to plot a histogram (and with what orientation)?
I specifically want them to be on the same axes just like the figure above and not on separate axes or subplots.
fig = plt.figure()
ax2Handler = fig.gca()
ax2Handler.scatter(np.array(np.arange(0,len(xData),1)), xData)
ax2Handler.hist(xData,bins=60,orientation='horizontal',normed=True)
This and other approaches (of inverting the axes) gave me no results. xData is loaded from a panda dataframe.
# This also doesn't work as intended
fig = plt.figure()
axHistHandler = fig.gca()
axScatterHandler = fig.gca()
axHistHandler.invert_xaxis()
axHistHandler.hist(xData,orientation='horizontal')
axScatterHandler.scatter(np.array(np.arange(0,len(xData),1)), xData)
A. using two axes
There is simply no reason not to use two different axes. The plot from the question can easily be reproduced with two different axes:
import numpy as np
import matplotlib.pyplot as plt
plt.style.use("ggplot")
xData = np.random.rand(1000)
fig,(ax,ax2)= plt.subplots(ncols=2, sharey=True)
fig.subplots_adjust(wspace=0)
ax2.scatter(np.linspace(0,1,len(xData)), xData, s=9)
ax.hist(xData,bins=60,orientation='horizontal',normed=True)
ax.invert_xaxis()
ax.spines['right'].set_visible(False)
ax2.spines['left'].set_visible(False)
ax2.tick_params(axis="y", left=0)
plt.show()
B. using a single axes
Just for the sake of answering the question: In order to plot both in the same axes, one can shift the bars by their length towards the left, effectively giving a mirrored histogram.
import numpy as np
import matplotlib.pyplot as plt
plt.style.use("ggplot")
xData = np.random.rand(1000)
fig,ax= plt.subplots(ncols=1)
fig.subplots_adjust(wspace=0)
ax.scatter(np.linspace(0,1,len(xData)), xData, s=9)
xlim1 = ax.get_xlim()
_,__,bars = ax.hist(xData,bins=60,orientation='horizontal',normed=True)
for bar in bars:
bar.set_x(-bar.get_width())
xlim2 = ax.get_xlim()
ax.set_xlim(-xlim2[1],xlim1[1])
plt.show()
You might be interested in seaborn jointplots:
# Import and fake data
import seaborn as sns
import numpy as np
import matplotlib.pyplot as plt
data = np.random.randn(2,1000)
# actual plot
jg = sns.jointplot(data[0], data[1], marginal_kws={"bins":100})
jg.ax_marg_x.set_visible(False) # remove the top axis
plt.subplots_adjust(top=1.15) # fill the empty space
produces this:
See more examples of bivariate distribution representations, available in Seaborn.

Pandas: let two lines auto scale to view the trend clearly

Suppose I have a dataframe df which has two columns, 100cos(x) and sin(x), if I plot it in one graph, it is not easy to view the trend of the second compare with first. How can I autoscale the second one? I just want to view the trend.
Here is the code to illustrate my point:
import pandas as pd
import matplotlib.pyplot as plt
x = np.arange(0,4*np.pi,0.02)
df = pd.DataFrame({'Sin':np.sin(x), 'Cos':100*np.cos(x)},index=x )
df.Sin.plot()
df.Cos.plot()
plt.show()
I want the blue line also takes almost the whole picture, please do not just multiply it by a number, the point is auto as if I just plot a single blue line in the figure.
In case you are too lazy to type much, consider the following solution, which does not need any extra line; just replace df.Cos.plot() by df.Cos.plot(ax=plt.gca().twinx(), color="C1").
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
x = np.arange(0,4*np.pi,0.02)
df = pd.DataFrame({'Sin':np.sin(x), 'Cos':100*np.cos(x)},index=x )
df.Sin.plot()
df.Cos.plot(ax=plt.gca().twinx(), color="C1")
plt.show()
However, I would strongly recommend looking closer at the matplotlib example, cited in epattaros answer, to understand what's happening.
An easy solution to that would be to use two different Y scales, one on each side, example from matplotlib page:
import numpy as np
import matplotlib.pyplot as plt
fig, ax1 = plt.subplots()
t = np.arange(0.01, 10.0, 0.01)
s1 = np.exp(t)
ax1.plot(t, s1, 'b-')
ax1.set_xlabel('time (s)')
# Make the y-axis label, ticks and tick labels match the line color.
ax1.set_ylabel('exp', color='b')
ax1.tick_params('y', colors='b')
ax2 = ax1.twinx()
s2 = np.sin(2 * np.pi * t)
ax2.plot(t, s2, 'r.')
ax2.set_ylabel('sin', color='r')
ax2.tick_params('y', colors='r')
fig.tight_layout()
plt.show()

Make matplotlib autoscaling ignore some of the plots

I use matplotib's Axes API to plot some figures. One of the lines I plot represents the theoretical expected line. It has no meaning outside of the original y and x limits. What I want, is for matlplotlib to ignore it when autoscaling the limits. What I used to do, is to check what are the current limits, then plot, and reset the limits. The problem is that when I plot a third plot, the limits get recalculated together with the theoretical line, and that really expands the graph.
# Boilerplate
from matplotlib.figure import Figure
from matplotlib.backends.backend_pdf import FigureCanvasPdf
from numpy import sin, linspace
fig = Figure()
ax = fig.add_subplot(1,1,1)
x1 = linspace(-1,1,100)
ax.plot(x1, sin(x1))
ax.plot(x1, 3*sin(x1))
# I wish matplotlib would not consider the second plot when rescaling
ax.plot(x1, sin(x1/2.0))
# But would consider the first and last
canvas_pdf = FigureCanvasPdf(fig)
canvas_pdf.print_figure("test.pdf")
The obvious way is to just manually set the limits to what you want. (e.g. ax.axis([xmin, xmax, ymin, ymax]))
If you don't want to bother with finding out the limits manually, you have a couple of options...
As several people (tillsten, Yann, and Vorticity) have mentioned, if you can plot the function you want to ignore last, then you can disable autoscaling before plotting it or pass the scaley=False kwarg to plot
import numpy as np
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
x1 = np.linspace(-1,1,100)
ax.plot(x1, np.sin(x1))
ax.plot(x1, np.sin(x1 / 2.0))
ax.autoscale(False) #You could skip this line and use scalex=False on
ax.plot(x1, 3 * np.sin(x1)) #the "theoretical" plot. It has to be last either way
fig.savefig('test.pdf')
Note that you can adjust the zorder of the last plot so that it's drawn in the "middle", if you want control over that.
If you don't want to depend on the order, and you do want to just specify a list of lines to autoscale based on, then you could do something like this: (Note: This is a simplified version assuming you're dealing with Line2D objects, rather than matplotlib artists in general.)
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.transforms as mtransforms
def main():
fig, ax = plt.subplots()
x1 = np.linspace(-1,1,100)
line1, = ax.plot(x1, np.sin(x1))
line2, = ax.plot(x1, 3 * np.sin(x1))
line3, = ax.plot(x1, np.sin(x1 / 2.0))
autoscale_based_on(ax, [line1, line3])
plt.show()
def autoscale_based_on(ax, lines):
ax.dataLim = mtransforms.Bbox.unit()
for line in lines:
xy = np.vstack(line.get_data()).T
ax.dataLim.update_from_data_xy(xy, ignore=False)
ax.autoscale_view()
if __name__ == '__main__':
main()
Use the scalex/scaley kw arg:
plot(x1, 3*sin(x1), scaley=False)
LineCollection objects can be ignored by using the autolim=False argument:
from matplotlib.collections import LineCollection
fig, ax = plt.subplots()
x1 = np.linspace(-1,1,100)
# Will update limits
ax.plot(x1, np.sin(x1))
# Will not update limits
col = LineCollection([np.column_stack((x1, 3 * np.sin(x1)))], colors='g')
ax.add_collection(col, autolim=False)
# Will still update limits
ax.plot(x1, np.sin(x1 / 2.0))
This can be done regardless of plotting order by creating another axes to work on.
In this version, we create a twin axes and disable the autoscaling on that twin axes. In this way, the plot is scaled based on anything plotted in the original axes, but is not scaled by anything put into the twin axes.
import numpy as np
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
x1 = np.linspace(-1,1,100)
twin_ax = ax.twinx() # Create a twin axes.
twin_ax.autoscale(False) # Turn off autoscaling on the twin axes.
twin_ax.set_yticks([]) # Remove the extra tick numbers from the twin axis.
ax.plot(x1, np.sin(x1))
twin_ax.plot(x1, 3 * np.sin(x1), c='green') # Plotting the thing we don't want to scale on in the twin axes.
ax.plot(x1, np.sin(x1 / 2.0))
twin_ax.set_ylim(ax.get_ylim()) # Make sure the y limits of the twin matches the autoscaled of the original.
fig.savefig('test.pdf')
Note, the above only prevents the un-twined axis from auto scaling (y in the above case). To get it to work for both x and y, we can do the twinning process for both x and y (or create the new axes from scratch):
import numpy as np
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
x1 = np.linspace(-1,1,100)
x2 = np.linspace(-2,2,100) # Would extend the x limits if auto scaled
twin_ax = ax.twinx().twiny() # Create a twin axes.
twin_ax.autoscale(False) # Turn off autoscaling on the twin axes.
twin_ax.set_yticks([]) # Remove the extra tick numbers from the twin axis.
twin_ax.set_xticks([]) # Remove the extra tick numbers from the twin axis.
ax.plot(x1, np.sin(x1))
twin_ax.plot(x2, 3 * np.sin(x2), c='green') # Plotting the thing we don't want to scale on in the twin axes.
ax.plot(x1, np.sin(x1 / 2.0))
twin_ax.set_ylim(ax.get_ylim()) # Make sure the y limits of the twin matches the autoscaled of the original.
twin_ax.set_xlim(ax.get_xlim()) # Make sure the x limits of the twin matches the autoscaled of the original.
fig.savefig('test.png')
As a generalisation of jam's answer, a collection object can be obtained from any of matplotlib's plotting functions and then re-added with autolim=False. For example,
fig, ax = plt.subplots()
x1 = np.linspace(-1,1,100)
# Get hold of collection
collection = ax.plot(x1, np.sin(x1))
# Remove collection from the plot
collection.remove()
# Rescale
ax.relim()
# Add the collection without autoscaling
ax.add_collection(collection, autolim=False)

Categories