Combine two matplotlib Figures, side by side, high quality - matplotlib

I produced two matplotlib Figures, at size of 1000x1000.
Each of the figures is 4x4 subplots based figure.
I want one figure at size of 1000x2000 (width is 2000).
fig1
<Figure size 1000x1000 with 4 Axes>
fig2
<Figure size 1000x1000 with 4 Axes>
Now I want to combine them together.
I've searched many references:
How to make two plots side-by-side using Python?
Plotting two figures side by side
Adding figures to subplots in Matplotlib
They are not relevant because mostly they suggest to change the way the initial plots were created. I don't want to change it - I want to use the Figure as is.
I just need to place Fig1 to the left of Fig2. Not changing the way Fig1 or Fig2 were created.
I also tried using PIL method: https://note.nkmk.me/en/python-pillow-concat-images/
However it was lower quality

You can render your figures to arrays using the agg backend.
Then concat the arrays side by side and switch back to your normal backend to show the result:
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
backend = mpl.get_backend()
mpl.use('agg')
dpi = 100
fig1,_ = plt.subplots(2,2, figsize=(1000/dpi, 1000/dpi), dpi=dpi)
fig1.suptitle('Figure 1')
fig2,_ = plt.subplots(2,2, figsize=(1000/dpi, 1000/dpi), dpi=dpi)
fig2.suptitle('Figure 2')
c1 = fig1.canvas
c2 = fig2.canvas
c1.draw()
c2.draw()
a1 = np.array(c1.buffer_rgba())
a2 = np.array(c2.buffer_rgba())
a = np.hstack((a1,a2))
mpl.use(backend)
fig,ax = plt.subplots(figsize=(2000/dpi, 1000/dpi), dpi=dpi)
fig.subplots_adjust(0, 0, 1, 1)
ax.set_axis_off()
ax.matshow(a)

Not directly merging two seperate figures, but I succeeded achieving the final goal by using this reference:
https://matplotlib.org/devdocs/gallery/subplots_axes_and_figures/subfigures.html
That's the code I needed:
fig = plt.figure(constrained_layout=True, figsize=(20, 11))
titles_size = 25
labels_size = 18
subfigs = fig.subfigures(1, 2, wspace=0.02)
subfigs[0].suptitle('Title 1', fontsize=titles_size)
subfigs[1].suptitle('Title 2', fontsize=titles_size)
axsLeft = subfigs[0].subplots(2, 2)
axsRight = subfigs[1].subplots(2, 2)
for ax_idx, ax in enumerate(axsLeft.reshape(-1)):
ax.grid(False)
ax.set_xticklabels([])
ax.set_yticklabels([])
ax.axes.xaxis.set_visible(False)
ax.axes.yaxis.set_visible(False)
for ax_idx, ax in enumerate(axsRight.reshape(-1)):
ax.grid(False)
ax.set_xticklabels([])
ax.set_yticklabels([])
ax.axes.xaxis.set_visible(False)
ax.axes.yaxis.set_visible(False)
plt.show()

Related

overlapping two plots in matplotlib

I've two plots generated using matplotlib. The first represents my backround and the second a group of points which I want to show. Is there a way to overlap the two plots?
background:
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize = (10,10))
grid_duomo = gpd.read_file('/content/Griglia_2m-SS.shp')
grid_duomo.to_crs(epsg=32632).plot(ax=ax, color='lightgrey')
points:
fig = plt.figure(figsize=(10, 10))
ids = traj_collection_df_new_app['id'].unique()
for id_ in ids:
self_id = traj_collection_df_new_app[traj_collection_df_new_app['id'] == id_]
plt.plot(
self_id['lon'],
self_id['lat'],
# markers= 'o',
# markersize=12
)
plt.plot() will always take the most recent axis found by matplotlib and use it for plotting.
Its practically the same as plt.gca().plot() where plt.gca() stands for "get current axis".
To get full control over which axis is used, you should do something like this:
(the zorder argument is used to set the "vertical stacking" of the artists, e.g. zorder=2 will be plotted on top of zorder=1)
f = plt.figure() # create a figure
ax = f.add_subplot( ... ) # create an axis in the figure f
ax.plot(..., zorder=0)
grid_duomo.plot(ax=ax, ..., zorder=1)
# you can then continue to add more axes to the same figure using
# f.add_subplot() or f.add_axes()
(if this is unclear, maybe check the quick_start guide of matplotlib? )

How to mirror the bars

I have two bars which I want to mirror. I have the following code
bar1 = df['nt'].value_counts().plot.barh()
bar2 = df1['nt'].value_counts().plot.barh()
bar1.set_xlim(bar1.get_xlim()[::-1])
# bar1.yaxis.tick_right()
But somehow not only the bar1 flips to the left(third line), but also the bar2. The same happening with the commented 4th line. Why is that? How to do it right then?
df...plot.barh()doesn't return bars nor a barplot. It returns theaxwhich indicates the subplot where the barplot was added. As both barplots are created onto the same subplot,set_xlim` etc. will act on that same subplot. This blogpost might be helpful.
To get two barplots, one from the left and one from the right, you could create a "twin" y -axis and then drawing one bar plot using the lower x-axis and the other user the upper x-axis. To make things clearer, the tick labels can be colored the same as the bars. To avoid overlapping bars, the x limits should be at least the maximum of the sum of the two value_counts.
import numpy as np
import pandas as pd
from matplotlib import pyplot as plt
df = pd.DataFrame({'nt': np.random.choice([*'abcdefhij'], 50)})
df1 = pd.DataFrame({'nt': np.random.choice([*'abcdefhij'], 50)})
max_sum_value_counts = df.append(df1).value_counts().max()
fig, ax = plt.subplots(figsize=(12, 5))
df['nt'].value_counts(sort=False).sort_index().plot.barh(ax=ax, color='purple')
ax.set_xlim(0, max_sum_value_counts + 1)
ax.tick_params(labelcolor='purple')
ax1 = ax.twiny()
df1['nt'].value_counts(sort=False).sort_index().plot.barh(ax=ax1, color='crimson')
ax1.set_xlim(max_sum_value_counts + 1, 0)
ax1.tick_params(labelcolor='crimson', labelright=True, labelleft=False)
ax1.invert_yaxis()
plt.show()

Pandas histogram plot with Y axis or colorbar

In Pandas, I am trying to generate a Ridgeline plot for which the density values are shown (either as Y axis or color-ramp). I am using the Joyplot but any other alternative ways are fine.
So, first I created the Ridge plot to show the different distribution plot for each condition (you can reproduce it using this code):
import pandas as pd
import joypy
import matplotlib
import matplotlib.pyplot as plt
df1 = pd.DataFrame({'Category1':np.random.choice(['C1','C2','C3'],1000),'Category2':np.random.choice(['B1','B2','B3','B4','B5'],1000),
'year':np.arange(start=1900, stop=2900, step=1),
'Data':np.random.uniform(0,1,1000),"Period":np.random.choice(['AA','CC','BB','DD'],1000)})
data_pivot=df1.pivot_table('Data', ['Category1', 'Category2','year'], 'Period')
fig, axes = joypy.joyplot(data_pivot, column=['AA', 'BB', 'CC', 'DD'], by="Category1", ylim='own', figsize=(14,10), legend=True, alpha=0.4)
so it generates the figure but without my desired Y axis. So, based on this post, I could add a colorramp, which neither makes sense nor show the differences between the distribution plot of the different categories on each line :) ...
ar=df1['Data'].plot.kde().get_lines()[0].get_ydata() ## a workaround to get the probability values to set the colorramp max and min
norm = plt.Normalize(ar.min(), ar.max())
original_cmap = plt.cm.viridis
cmap = matplotlib.colors.ListedColormap(original_cmap(norm(ar)))
sm = matplotlib.cm.ScalarMappable(cmap=original_cmap, norm=norm)
sm.set_array([])
# plotting ....
fig, axes = joypy.joyplot(data_pivot,colormap = cmap , column=['AA', 'BB', 'CC', 'DD'], by="Category1", ylim='own', figsize=(14,10), legend=True, alpha=0.4)
fig.colorbar(sm, ax=axes, label="density")
But what I want is some thing like either of these figures (preferably with colorramp) :

Multiple different kinds of plots on a single figure and save it to a video

I am trying to plot multiple different plots on a single matplotlib figure with in a for loop. At the moment it is all good in matlab as shown in the picture below and then am able to save the figure as a video frame. Here is a link of a sample video generated in matlab for 10 frames
In python, tried it as below
import matplotlib.pyplot as plt
for frame in range(FrameStart,FrameEnd):#loop1
# data generation code within a for loop for n frames from source video
array1 = np.zeros((200, 3800))
array2 = np.zeros((19,2))
array3 = np.zeros((60,60))
for i in range(len(array2)):#loop2
#generate data for arrays 1 to 3 from the frame data
#end loop2
plt.subplot(6,1,1)
plt.imshow(DataArray,cmap='gray')
plt.subplot(6, 1, 2)
plt.bar(data2D[:,0], data2D[:,1])
plt.subplot(2, 2, 3)
plt.contourf(mapData)
# for fourth plot, use array2[3] and array2[5], plot it as shown and keep the\is #plot without erasing for next frame
not sure how to do the 4th axes with line plots. This needs to be there (done using hold on for this axis in matlab) for the entire sequence of frames processing in the for loop while the other 3 axes needs to be erased and updated with new data for each frame in the movie. The contour plot needs to be square all the time with color bar on the side. At the end of each frame processing, once all the axes are updated, it needs to be saved as a frame of a movie. Again this is easily done in matlab, but not sure in python.
Any suggestions
thanks
I guess you need something like this format.
I have used comments # in code to answer your queries. Please check the snippet
import matplotlib.pyplot as plt
fig=plt.figure(figsize=(6,6))
ax1=fig.add_subplot(311) #3rows 1 column 1st plot
ax2=fig.add_subplot(312) #3rows 1 column 2nd plot
ax3=fig.add_subplot(325) #3rows 2 column 5th plot
ax4=fig.add_subplot(326) #3rows 2 column 6th plot
plt.show()
To turn off ticks you can use plt.axis('off'). I dont know how to interpolate your format so left it blank . You can adjust your figsize based on your requirements.
import numpy as np
from numpy import random
import matplotlib.pyplot as plt
fig=plt.figure(figsize=(6,6)) #First is width Second is height
ax1=fig.add_subplot(311)
ax2=fig.add_subplot(312)
ax3=fig.add_subplot(325)
ax4=fig.add_subplot(326)
#Bar Plot
langs = ['C', 'C++', 'Java', 'Python', 'PHP']
students = [23,17,35,29,12]
ax2.bar(langs,students)
#Contour Plot
xlist = np.linspace(-3.0, 3.0, 100)
ylist = np.linspace(-3.0, 3.0, 100)
X, Y = np.meshgrid(xlist, ylist)
Z = np.sqrt(X**2 + Y**2)
cp = ax3.contourf(X, Y, Z)
fig.colorbar(cp,ax=ax3) #Add a colorbar to a plot
#Multiple line plot
x = np.linspace(-1, 1, 50)
y1 = 2*x + 1
y2 = 2**x + 1
ax4.plot(x, y2)
ax4.plot(x, y1, color='red',linewidth=1.0)
plt.tight_layout() #Make sures plots dont overlap
plt.show()

Matplotlib - 10 x 10 matrix only populating last instance

I am trying to plot 100 random images from the notMNIST dataset in a 10x10 matrix, however only the last subplot is returning an image. Image data is stored in x, and labels in y.
import matplotlib.pyplot as plt
%matplotlib inline
num_subplots = 10
fig, ax = plt.subplots(nrows=num_subplots, ncols=num_subplots, figsize=(10, 6))
for idx in range(10):
n = np.random.randint(np.sum(y < 0), len(y))
imgl = x[n,:].reshape((28,28))
imshow(imgl, cmap = plt.get_cmap('gray'))
plt.show()
If you call plt.imshow(), the plot will always appear at the currently active subplot which, in your case, is the last subplot that has been created. Instead, you can loop over all subplots and call imshow() on the subplots themselves using the object oriented matplotlib syntax. As you generate an array of subplots, the subplots() function returns a 2d numpy array, wich you first need to convert into a 1D iterable, which you can achieve with the ravel() function. See the below code for a complete example.
import matplotlib.pyplot as plt
%matplotlib inline
num_subplots = 10
fig, axes = plt.subplots(nrows=num_subplots, ncols=num_subplots, figsize=(10, 6))
for idx,ax in enumerate(axes.ravel()):
n = np.random.randint(np.sum(y < 0), len(y))
imgl = x[n,:].reshape((28,28))
ax.imshow(imgl, cmap = plt.get_cmap('gray'))
plt.show()
Note that I renamed the original ax to axes to make it more apparent that you have many subplots (Axes instances). Note also that, if you want more control, you could loop over the rows and columns of your subplot arrangement separately. In this case you would use two for loops and access your subplots using axes[row,col], something like this:
for row in range(num_subplots):
for col in range(num_subplots):
#some code here
axes[row,col].imshow(...)
Hope this helps.