I found the following example on matplotlib:
import numpy as np
import matplotlib.pyplot as plt
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')
plt.subplot(2, 1, 2)
plt.plot(x2, y2, 'r.-')
plt.xlabel('time (s)')
plt.ylabel('Undamped')
plt.show()
My question is: What do i need to change, to have the plots side-by-side?
Change your subplot settings to:
plt.subplot(1, 2, 1)
...
plt.subplot(1, 2, 2)
The parameters for subplot are: number of rows, number of columns, and which subplot you're currently on. So 1, 2, 1 means "a 1-row, 2-column figure: go to the first subplot." Then 1, 2, 2 means "a 1-row, 2-column figure: go to the second subplot."
You currently are asking for a 2-row, 1-column (that is, one atop the other) layout. You need to ask for a 1-row, 2-column layout instead. When you do, the result will be:
In order to minimize the overlap of subplots, you might want to kick in a:
plt.tight_layout()
before the show. Yielding:
Check this page out: http://matplotlib.org/examples/pylab_examples/subplots_demo.html
plt.subplots is similar. I think it's better since it's easier to set parameters of the figure. The first two arguments define the layout (in your case 1 row, 2 columns), and other parameters change features such as figure size:
import numpy as np
import matplotlib.pyplot as plt
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)
fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(5, 3))
axes[0].plot(x1, y1)
axes[1].plot(x2, y2)
fig.tight_layout()
When stacking subplots in one direction, the matplotlib documentation advocates unpacking immediately if you are just creating a few axes.
fig, (ax1, ax2) = plt.subplots(1,2, figsize=(20,8))
sns.histplot(df['Price'], ax=ax1)
sns.histplot(np.log(df['Price']),ax=ax2)
plt.show()
You can use - matplotlib.gridspec.GridSpec
Check - https://matplotlib.org/stable/api/_as_gen/matplotlib.gridspec.GridSpec.html
The below code displays a heatmap on right and an Image on left.
#Creating 1 row and 2 columns grid
gs = gridspec.GridSpec(1, 2)
fig = plt.figure(figsize=(25,3))
#Using the 1st row and 1st column for plotting heatmap
ax=plt.subplot(gs[0,0])
ax=sns.heatmap([[1,23,5,8,5]],annot=True)
#Using the 1st row and 2nd column to show the image
ax1=plt.subplot(gs[0,1])
ax1.grid(False)
ax1.set_yticklabels([])
ax1.set_xticklabels([])
#The below lines are used to display the image on ax1
image = io.imread("https://images-na.ssl-images- amazon.com/images/I/51MvhqY1qdL._SL160_.jpg")
plt.imshow(image)
plt.show()
Output image
Basically we have to define how many rows and columns we require.
Lets Say we have total 4 categorical columns to be plotted. Lets have total 4 plots in 2 rows and 2 columns.
import matplotlib.pyplot as plt
import matplotlib
import seaborn as sns
sns.set_style("darkgrid")
%matplotlib inline
#15 by 15 size set for entire plots
plt.figure(figsize=(15,15));
#Set rows variable to 2
rows = 2
#Set columns variable to 2, this way we will plot 2 by 2 = 4 plots
columns = 2
#Set the plot_count variable to 1
#This variable will be used to define which plot out of total 4 plot
plot_count = 1
cat_columns = [col for col in df.columns if df[col].dtype=='O']
for col in cat_columns:
plt.subplot(rows, columns, plot_count)
sns.countplot(x=col, data=df)
plt.xticks(rotation=70);
#plot variable is incremented by 1 till 4, specifying which plot of total 4 plots
plot_count += 1
Related
I have configured subplots of (5 x 1) format shown in Fig. 1 as given by Figure block A in the MWE. I am trying to repeat them n times such that they appear in a grid format with number of rows and columns given by the function fitPlots as mentioned here; to give output as shown in Fig. 2.
Fig. 1 Initial plot
Fig. 2 Repeated plot (desired output)
What would be the best way to repeat the code block to create a grid plot with inner subplots? The MWE creates multiple pages, I want all of them on a single page.
MWE
from matplotlib.backends.backend_pdf import PdfPages
import matplotlib.pyplot as plt
import numpy as np
import math
x = np.arange(1, 100, 0.2)
y_a = np.sqrt(x)
y_b = np.sin(x)
y_c = np.sin(x)
y_d = np.cos(x) * np.cos(x)
y_e = 1/x
########## Figure block A #####################
with PdfPages('./plot_grid.pdf') as plot_grid_loop:
fig, (a, b, c, d, e) = plt.subplots(5, 1, sharex=True, gridspec_kw={'height_ratios': [5, 1, 1, 1, 1]})
a.plot(x, y_a)
b.plot(x, y_b)
c.plot(x, y_c)
d.plot(x, y_d)
e.plot(x, y_e)
plot_grid_loop.savefig()
plt.close
########## Figure block A #####################
# from https://stackoverflow.com/a/43366784/4576447
def fitPlots(N, aspect=(16,9)):
width = aspect[0]
height = aspect[1]
area = width*height*1.0
factor = (N/area)**(1/2.0)
cols = math.floor(width*factor)
rows = math.floor(height*factor)
rowFirst = width < height
while rows*cols < N:
if rowFirst:
rows += 1
else:
cols += 1
rowFirst = not(rowFirst)
return rows, cols
n_plots = 15
n_rows, n_cols = fitPlots(n_plots)
with PdfPages('./plot_grid.pdf') as plot_grid_loop:
for m in range(1, n_plots+1):
fig, (a, b, c, d, e) = plt.subplots(5, 1, sharex=True, gridspec_kw={'height_ratios': [5, 1, 1, 1, 1]})
a.plot(x, y_a)
b.plot(x, y_b)
c.plot(x, y_c)
d.plot(x, y_d)
e.plot(x, y_e)
plot_grid_loop.savefig()
plt.close
This can be done by generating a GridSpec object with gs_fig = fig.add_gridspec() that contains enough rows and columns to fit the five figure blocks (note that when you use plt.subplots a GridSpec is also generated and can be accessed with ax.get_gridspec()). Each empty slot in the GridSpec can then be filled with a sub-GridSpec with gs_sub = gs_fig[i].subgridspec() to hold the five subplots. The trickier part is sharing the x-axis. This can be done by generating an empty first Axes with which the x-axis of all the subplots can be shared.
The following example illustrates this with only three figure blocks, based on the code sample you have shared but with some differences regarding the figure design: the number of rows is computed based on the chosen number of columns, and the figure dimensions are set based on a chosen figure width and aspect ratio. The code for saving the figure to a pdf file is not included.
import numpy as np # v 1.19.2
import matplotlib.pyplot as plt # v 3.3.4
# Create variables to plot
x = np.arange(1, 100, 0.2)
y_a = np.sqrt(x)
y_b = np.sin(x)
y_c = np.sin(x)
y_d = np.cos(x)*np.cos(x)
y_e = 1/x
# Set parameters for figure dimensions
nplots = 3 # random number of plots for this example
ncols = 2
nrows = int(np.ceil(nplots/ncols))
subp_w = 10/ncols # 10 is the total figure width in inches
subp_h = 1*subp_w # set subplot aspect ratio
# Create figure containing GridSpec object with appropriate dimensions
fig = plt.figure(figsize=(ncols*subp_w, nrows*subp_h))
gs_fig = fig.add_gridspec(nrows, ncols)
# Loop through GridSpec to add sub-GridSpec for each figure block
heights = [5, 1, 1, 1, 1]
for i in range(nplots):
gs_sub = gs_fig[i].subgridspec(len(heights), 1, height_ratios=heights, hspace=0.2)
ax = fig.add_subplot(gs_sub[0, 0]) # generate first empty Axes to enable sharex
ax.axis('off') # remove x and y axes because it is overwritten in the loop below
# Loop through y variables to plot all the subplots with shared x-axis
for j, y in enumerate([y_a, y_b, y_c, y_d, y_e]):
ax = fig.add_subplot(gs_sub[j, 0], sharex=ax)
ax.plot(x, y)
if not ax.is_last_row():
ax.tick_params(labelbottom=False)
Reference: matplotlib tutorial GridSpec using SubplotSpec
This question already has an answer here:
Set subplot(or gridspec) with same size
(1 answer)
Closed 3 years ago.
I am trying to plot 3 graphs in a single row, but all plots should be the same size (at least the same height).
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.gridspec as gridspec
x = np.random.uniform(size=2000) - 0.5
y = np.random.uniform(size=2000) - 0.5
z = np.random.uniform(size=2000) - 0.5
DPI = 106
fig = plt.figure(figsize=(900 / DPI, 350 / DPI))
gs = gridspec.GridSpec(1, 3, width_ratios=[1,1,1])
# 1D
r = np.abs(x)
# plot
ax1 = fig.add_subplot(gs[0])
plot = ax1.scatter(x, r, s = 1, c=r, cmap='jet', marker='.', alpha = 1, vmax = 0.5)
ax1.set_xlabel('$x$')
ax1.set_ylabel('$y$')
ax1.set_aspect('equal')
# 2D
r = np.sqrt(x * x + y * y)
# plot
ax2 = fig.add_subplot(gs[1])
plot = ax2.scatter(x, y, s = 1, c=r, cmap='jet', marker='.', alpha = 1, vmax = 0.5)
ax2.set_xlabel('$x$')
ax2.set_ylabel('$y$')
ax2.set_aspect('equal')
fig.colorbar(plot, shrink = 1, ax = ax2)
# 3D
r = np.sqrt(x * x + y * y + z * z)
ax3 = fig.add_subplot(gs[2], projection='3d')
plot = ax3.scatter(x, y, z, s = 10, c=r, cmap='jet', marker='.', alpha = 1, vmax = 0.5)
ax3.set_xlabel('$x$')
ax3.set_ylabel('$y$')
ax3.set_zlabel('$z$')
ax3.view_init(30, 240)
ax3.set_aspect('equal', 'box')
fig.colorbar(plot, shrink = 1,ax = ax3)
fig.tight_layout()
The code above does produce three columns of plots, first 1D, than 2D and lastly a 3D plot. However, as you can see from attached image, the plots are not of the same size eventhough I tried using gridspecas suggested here.
Any ideas on how to change the size of subplots?
Matplotlib.pyplot's auto-layout algorithm does not care if you are plotting a 3D object, 2D object or 1D (points). Of course the syntax for defining the object will change and a 3D object will take 3 parameters. But how the objects are placed in a row is not changed. I see a few possible reasons why your specific data may be causing you trouble.
The first image is 2D and has a smaller y-axis scale than the other 2 images. Also the 1st image x-axis scale is twice as wide as y-axis scale is tall. The 2nd and 3rd images include vertical colormaps, which makes these images taller in total.
1) You can change the 1st plot's y-axis to be taller than it currently is.
ax1.set_aspect('equal')
This code in your 1st plot is preventing you from changing the y-axis scale only. You can remove this line and manually set the scale of y-axis scale to be larger.
2) Make your entire row taller, so the vertical colormaps in 2nd and 3rd plots will not determine the overall height of the figure space. Set the figsize's x and y attribute to (12, 12) and see if that fixes the issue. The 2nd number in figsize sets height.
fig, axes = plt.subplots(nrows=1, ncols=3, figsize=(12, 12))
3) Alternatively, you can plot 1st graph in it's own 1st row, and 2nd and 3rd graphs in a separate 2nd row. Set nrows to 2 and ncols to 2, then add 1st plot to row 1 and col 1, and 2nd plot to row 2, col 1, and 3rd plot to row 2, col 2.
fig, axes = plt.subplots(nrows=2, ncols=2, figsize=(12, 18))
You can refer to Matplotlib documentation for getting details setting layout parameters. Hope one of these will work. :-)
https://matplotlib.org/3.1.0/api/_as_gen/matplotlib.axes.Axes.set_aspect.html
I am having a set of different times series which can be grouped. E.g. the plot below shows series A, B, C and D. However, A and B are in group G1 and C and D are in group G2.
I would like to reflect that in the plot by adding another axis on the left which goes across groups of turbines and label thes axis accordingly.
I've tried a few thing so far but apparently that one's not so easy.
Does some body know how I can do that?
PS: Since I am using panda's plot(subplots=True) on a data frame which has already columns
| G1 | G2 |
|-------|------|
index | A B | C D |
------|-------|------|
it might be that pandas can do that already for me. That's why I am using the pandas tag.
You can create additional axes in the plot, which span each two plots but only have a left y-axis, no ticks and other decorations. Only a ylabel is set. This will make the whole thing look well aligned.
The good thing is that you can work with your existing pandas plot. The drawback is that is more than 15 lines of code.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
df = pd.DataFrame(np.random.rand(26,4), columns=list("ABCD"))
axes = df.plot(subplots=True)
fig = axes[0].figure
gs = gridspec.GridSpec(4,2)
gs.update(left=0.1, right=0.48, wspace=0.05)
fig.subplots_adjust(left=.2)
for i, ax in enumerate(axes):
ax.set_subplotspec(gs[i,1])
aux1 = fig.add_subplot(gs[:2,0])
aux2 = fig.add_subplot(gs[2:,0])
aux1.set_ylabel("G1")
aux2.set_ylabel("G2")
for ax in [aux1, aux2]:
ax.tick_params(size=0)
ax.set_xticklabels([])
ax.set_yticklabels([])
ax.set_facecolor("none")
for pos in ["right", "top", "bottom"]:
ax.spines[pos].set_visible(False)
ax.spines["left"].set_linewidth(3)
ax.spines["left"].set_color("crimson")
plt.show()
Here is an example I came up with. Since you did not provide your code, I did it without pandas, because I am not proficient with it.
You basically plot as one would and then create another axis around all your previous ones, remove its axis with ax5.axis('off') and plot the 2 lines and text on it.
from matplotlib import lines
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 4*np.pi, 100)
y1 = np.sin(x)
y2 = np.cos(x)
y3 = np.tan(x)
y4 = np.cos(x)/(x+1)
fig = plt.figure()
fig.subplots_adjust(hspace=.5)
ax1 = plt.subplot(411)
ax1.plot(x, y1)
ax2 = plt.subplot(412)
ax2.plot(x, y2)
ax3 = plt.subplot(413)
ax3.plot(x, y3)
ax4 = plt.subplot(414)
ax4.plot(x, y4)
# new axis around the others with 0-1 limits
ax5 = plt.axes([0, 0, 1, 1])
ax5.axis('off')
line_x1, line_y1 = np.array([[0.05, 0.05], [0.05, 0.5]])
line1 = lines.Line2D(line_x1, line_y1, lw=2., color='k')
ax5.add_line(line1)
line_x2, line_y2 = np.array([[0.05, 0.05], [0.55, 0.9]])
line2 = lines.Line2D(line_x2, line_y2, lw=2., color='k')
ax5.add_line(line2)
ax5.text(0.0, 0.75, "G1")
ax5.text(0.0, 0.25, "G2")
plt.show()
Inspired by How to draw a line outside of an axis in matplotlib (in figure coordinates)?
I am looking for something similar to arrangeGrob in R:
I have a function (say, function FUN1) that creates a plot with subplots. The number of subplots FUN1 creates may vary and the plot itself is quite complex. I have two other functions FUN2 and FUN3 which also create plots of varying structure.
Is there a simple way to define/arrange an overall GRID, for example a simple 3 rows 1 column style and simply pass
FUN1 --> GRID(row 1, col 1)
FUN2 --> GRID(row 2, col 1)
FUN3 --> GRID(row 3, col 1)
afterwards such that the complicated plot generated by FUN1 gets plotted in in row 1, the plot generated by FUN2 in row 2 and so on, without specifying the subplot criteria in the FUNs before?
The usual way to create plots with matplotlib would be to create some axes first and then plot to those axes. The axes can be set up on a grid using plt.subplots, figure.add_subplot, plt.subplot2grid or more sophisticated, using GridSpec.
Once those axes are created, they can be given to functions, which plot content to the axes. The following would be an example where 6 axes are created and 3 different functions are used to plot to them.
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import numpy as np
def func1(ax, bx, cx):
x = np.arange(3)
x2 = np.linspace(-3,3)
y1 = [1,2,4]
y2 = [3,2.5,3.4]
f = lambda x: np.exp(-x**2)
ax.bar(x-0.5, y1, width=0.4)
ax.bar(x, y2, width=0.4)
bx.plot(x,y1, label="lab1")
bx.scatter(x,y2, label="lab2")
bx.legend()
cx.fill_between(x2, f(x2))
def func2(ax, bx):
x = np.arange(1,18)/1.9
y = np.arange(1,6)/1.4
z = np.outer(np.sin(x), -np.sqrt(y)).T
ax.imshow(z, aspect="auto", cmap="Purples_r")
X, Y = np.meshgrid(np.linspace(-3,3),np.linspace(-3,3))
U = -1-X**2+Y
V = 1+X-Y**2
bx.streamplot(X, Y, U, V, color=U, linewidth=2, cmap="autumn")
def func3(ax):
data = [sorted(np.random.normal(0, s, 100)) for s in range(2,5)]
ax.violinplot(data)
gs = gridspec.GridSpec(3, 4,
width_ratios=[1,1.5,0.75,1], height_ratios=[3,2,2] )
ax1 = plt.subplot(gs[0:2,0])
ax2 = plt.subplot(gs[2,0:2])
ax3 = plt.subplot(gs[0,1:3])
ax4 = plt.subplot(gs[1,1])
ax5 = plt.subplot(gs[0,3])
ax6 = plt.subplot(gs[1:,2:])
func1(ax1, ax3, ax5)
func3(ax2)
func2(ax4, ax6)
plt.tight_layout()
plt.show()
I have two arrays. One is the raw signal of length (1000, ) and the other one is the smooth signal of length (100,). I want to visually represent how the smooth signal represents the raw signal. Since these arrays are of different length, I am not able to plot them one over the other. Is there a way to do so in matplotlib?
Thanks!
As rth suggested, define
x1 = np.linspace(0, 1, 1000)
x2 = np.linspace(0, 1, 100)
and then plot raw versus x1, and smooth versus x2:
plt.plot(x1, raw)
plt.plot(x2, smooth)
np.linspace(0, 1, N) returns an array of length N with equally spaced values from 0 to 1 (inclusive).
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(2015)
raw = (np.random.random(1000) - 0.5).cumsum()
smooth = raw.reshape(-1,10).mean(axis=1)
x1 = np.linspace(0, 1, 1000)
x2 = np.linspace(0, 1, 100)
plt.plot(x1, raw)
plt.plot(x2, smooth)
plt.show()
yields
You will need two different x-axes for this job. You cannot plot two variables with different lengths in one single plot.
import matplotlib.pyplot as plt
import numpy as np
y = np.random.random(100) # the smooth signal
x = np.linspace(0,100,100) # it's x-axis
y1 = np.random.random(1000) # the raw signal
x1 = np.linspace(0,100,1000) # it's x-axis
fig = plt.figure()
ax = fig.add_subplot(121)
ax.plot(x,y,label='smooth-signal')
ax.legend(loc='best')
ax2 = fig.add_subplot(122)
ax2.plot(x1,y1,label='raw-signal')
ax2.legend(loc='best')
plt.suptitle('Smooth-vs-raw signal')
fig.show()