Adjusting gridspec so that plotted data aligns - python

I have several graphs to plot, all having the width a multiple of some unit as in the figure below.
So the bottom axis is 1/4 of the whole width, the second-to-bottom one is 2/4 of the width etc.
The code I am using:
import matplotlib.pyplot as plt
divs = 4
fig = plt.figure()
gs = fig.add_gridspec(ncols = divs, nrows = divs)
axes = [fig.add_subplot(gs[div, div:]) for div in range(divs)]
for row in range(divs):
axes[row].plot([1]*10*(divs - row), c = 'r')
axes[row].set_xlabel('', fontsize = 6)
fig.set_figheight(10)
fig.set_figwidth(10)
plt.show()
My problem is that the plots don't exactly align as I want them to: The plot on row 2 begins slightly to the right of the '10' tick mark on the plot on row 1, and the same applies for the plot on row 3 vs the plot on row 2 etc. I would like the beginning of the plot on row 2 to synchronize precisely with the '10' on row 1, and likewise for the other plots. How is this achievable (not necessarily but preferably using gridspec)?
I tried adding axes[row].tick_params(axis="y",direction="in", pad=-22) to push the y-axis inside the plot but that didn't change the alignment. Also I tried using fig.tight_layout(pad = 0.3): this did not change the alignment either.

If you set the default value of the margin of the graph X-axis to 0, the ticks will match.
import matplotlib.pyplot as plt
divs = 4
fig = plt.figure()
gs = fig.add_gridspec(ncols = divs, nrows = divs)
plt.rcParams['axes.xmargin'] = 0.0 #updated
axes = [fig.add_subplot(gs[div, div:]) for div in range(divs)]
for row in range(divs):
axes[row].plot([1]*10*(divs - row), c = 'r')
axes[row].set_xlabel('', fontsize = 6)
fig.set_figheight(10)
fig.set_figwidth(10)
plt.show()
subplots(gridspec_kw=()...)
import matplotlib.pyplot as plt
divs = 4
fig, axes = plt.subplots(4,1, gridspec_kw=dict(height_ratios=[1,1,1,1]), sharex='col', figsize=(10,10))
for row in range(divs):
axes[row].plot([1]*10*(divs - row), c = 'r')
axes[row].set_xlabel('', fontsize = 6)
plt.show()

Related

Seperate title for each subplot in a for loop in Python

I am trying to use subplots within a for loop and I can plot all my graphs, but I can't give them individual x and y labels and titles. It is only the last one that it is applied to.
import numpy as np
import astropy
import matplotlib.pyplot as plt
import pandas as pd
#Import 18 filesnames with similar names
from glob import glob
filenames = glob('./*V.asc')
df = [np.genfromtxt(f) for f in filenames]
A = np.stack(df, axis=0)
#Begin subplot
nrows = 3
ncols = 6
fig, ax = plt.subplots(nrows = nrows, ncols = ncols, figsize=(30,15))
#Loop over each filename i, row j and column k
i = 0
for j in range(0, nrows):
for k in range(0, ncols):
ax[j,k].plot(A[i,:,0], A[i,:,1])
plt.title(filenames[i], fontsize = '25')
i += 1
plt.subplots_adjust(wspace=.5, hspace=.5)
fig.show()
I can plot it in seperate plots, so 18 in total and it works fine
for i in range(0, len(A)):
plt.figure(i)
plt.title(filenames[i], fontsize = '30')
plt.plot(A[i,:,0], A[i,:,1])
plt.xlabel('Wavelength [Å]', fontsize = 20)
plt.ylabel('Flux Density [erg/s/cm^2/Å]', fontsize = 20)
plt.xticks(fontsize = 20)
plt.yticks(fontsize = 20)
I update the title each iteration i, same as the subplot, so I don't understand why it doesn't work.
Any input is appreciated!
plt.title() acts on the current axes, which is generally the last created, and not the Axes that you are thinking of.
In general, if you have several axes, you will be better off using the object-oriented interface of matplotlib rather that the pyplot interface. See usage guide
replace:
plt.title(filenames[i], fontsize = '25')
by
ax[j,k].set_title(filenames[i], fontsize = '25')

How to have a secondary y axis in a nested GridSpec?

I'd like to obtain this figure:
But with two plots inside each graph, like this:
Here is a sample of the code I used for the first figure
measures = ['ACE', 'SCE', 'LZs', 'LZc']
conditions = ['dark','light','flick3','flick10','switch']
outer_grid = gridspec.GridSpec(2,2)
for measure in measures:
inner_grid = gridspec.GridSpecFromSubplotSpec(5, 1, subplot_spec=outer_grid[measures.index(measure)])
ax={}
for cond in conditions:
c=conditions.index(cond)
ax[c] = plt.Subplot(fig, inner_grid[c])
if c != 0:
ax[c].get_shared_y_axes().join(ax[0], ax[c])
ax[c].plot()
ax[c+n]=ax[c].twinx()
ax[c+n].scatter()
ax[c+n].set_ylim(0,5)
fig.add_subplot(ax[c],ax[c+n])
For the second plot, it's basically the same without the first loop and GridSpec, using ax[c]=plt.subplot('51{c}') instead of ax[c]=plt.Subplot(fig, inner_grid[c]).
As you can see, when using GridSpec I still have the secondary y axis but not the scatter plot associated.
I guess the short question would be How to write fig.add_subplot(ax[c],ax[c+n]) properly?
(fig.add_subplot(ax[c]) fig.add_subplot(ax[c+n]) in two lines doesn't work.)
It is not clear from your question exactly which data you're plotting in each subplot, plus the way you're creating your subplots seems a little convoluted, which is probably why you're having problems. Here is how I would do it:
import matplotlib.gridspec as gs
measures = ['ACE', 'SCE', 'LZs', 'LZc']
conditions = ['dark','light','flick3','flick10','switch']
colors = ['g','c','b','r','grey']
Npoints = 10
data = [np.random.random((Npoints,len(measures))) for i in range(len(conditions))]
gs00 = gs.GridSpec(len(conditions), 1)
fig = plt.figure(figsize=(5,5))
for i,condition in enumerate(conditions):
ax1 = fig.add_subplot(gs00[i])
ax2 = ax1.twinx()
ax1.plot(range(Npoints), data[i][:,0], 'o-', color=colors[i], label=measures[0])
ax2.plot(range(Npoints), data[i][:,1], 'o-.', color=colors[i], label=measures[1])
ax1.set_ylim((-0.1,1.1))
ax2.set_ylim(ax1.get_ylim())
ax1.set_title(condition)
EDIT to get the same thing repeated 4 times, the logic is exactly the same, you just have to play around with the gridspec. But the only things that matters are the lines ax1 = fig.add_subplot(gs01[j]) followed by ax2 = ax1.twinx(), which will create a second axis on top of the first
import matplotlib.gridspec as gs
measures = ['ACE', 'SCE', 'LZs', 'LZc']
conditions = ['dark','light','flick3','flick10','switch']
colors = ['g','c','b','r','grey']
Npoints = 10
data = [np.random.random((Npoints,len(measures))) for i in range(len(conditions))]
gs00 = gs.GridSpec(2,2)
plt.style.use('seaborn-paper')
fig = plt.figure(figsize=(10,10))
grid_x, grid_y = np.unravel_index(range(len(measures)),(2,2))
for i,measure in enumerate(measures):
gs01 = gs.GridSpecFromSubplotSpec(len(conditions), 1, subplot_spec=gs00[grid_x[i],grid_y[i]])
for j,condition in enumerate(conditions):
ax1 = fig.add_subplot(gs01[j])
ax2 = ax1.twinx()
ax1.plot(range(Npoints), data[j][:,0], 'o-', color=colors[j], label=measures[0])
ax2.plot(range(Npoints), data[j][:,1], 'o-.', color=colors[j], label=measures[1])
ax1.set_ylim((-0.1,1.1))
ax2.set_ylim(ax1.get_ylim())
if j==0:
ax1.set_title(measure)

Pandas combine multiple subplots with same x axis into 1 bar chart

I am looping through a list containing 6 col_names. I loop by taking 3 cols at a time so i can print 3 subplots per iteration later.
I have 2 dataframes with same column names so they look identical except for the histograms of each column name.
I want to plot similar column names of both dataframes on the same subplot. Right now, im plotting their histograms on 2 separate subplots.
currently, for col 'A','B','C' in df_plot:
and for col 'A','B','C' in df_plot2:
I only want 3 charts where i can combine similar column names into same chart so there is blue and yellow bars in the same chart.
Adding df_plot2 below doesnt work. i think im not defining my second axs properly but im not sure how to do that.
col_name_list = ['A','B','C','D','E','F']
chunk_list = [col_name_list[i:i + 3] for i in xrange(0, len(col_name_list), 3)]
for k,g in enumerate(chunk_list):
df_plot = df[g]
df_plot2 = df[g][df[g] != 0]
fig, axs = plt.subplots(1,len(g),figsize = (50,20))
axs = axs.ravel()
for j,x in enumerate(g):
df_plot[x].value_counts(normalize=True).head().plot(kind='bar',ax=axs[j], position=0, title = x, fontsize = 30)
# adding this doesnt work.
df_plot2[x].value_counts(normalize=True).head().plot(kind='bar',ax=axs[j], position=1, fontsize = 30)
axs[j].title.set_size(40)
fig.tight_layout()
the solution is to plot on the same ax:
change axs[j] to axs
for k,g in enumerate(chunk_list):
df_plot = df[g]
df_plot2 = df[g][df[g] != 0]
fig, axs = plt.subplots(1,len(g),figsize = (50,20))
axs = axs.ravel()
for j,x in enumerate(g):
df_plot[x].value_counts(normalize=True).head().plot(kind='bar',ax=axs, position=0, title = x, fontsize = 30)
# adding this doesnt work.
df_plot2[x].value_counts(normalize=True).head().plot(kind='bar',ax=axs, position=1, fontsize = 30)
axs[j].title.set_size(40)
fig.tight_layout()
then just call plt.plot()
Example this will plot x and y on the same subplot:
import matplotlib.pyplot as plt
x = np.arange(0, 10, 1)
y = np.arange(0, 20, 2)
ax = plt.subplot(1,1)
fig = plt.figure()
ax = fig.gca()
ax.plot(x)
ax.plot(y)
plt.show()
EDIT:
There is now a squeeze keyword argument. This makes sure the result is always a 2D numpy array.
fig, ax2d = subplots(2, 2, squeeze=False)
if needed Turning that into a 1D array is easy:
axli = ax1d.flatten()

Row Titles within a matplotlib GridSpec

I have an GridSpec defined layout with to subgrids, one is supposed to include a colorbar
import pylab as plt
import numpy as np
gs_outer = plt.GridSpec(1, 2, width_ratios=(10, 1))
gs_inner = plt.matplotlib.gridspec.GridSpecFromSubplotSpec(2, 3, gs_outer[0])
ax = []
for i in xrange(6):
ax.append(plt.subplot(gs_inner[i]))
plt.setp(ax[i].get_xticklabels(), visible=False)
plt.setp(ax[i].get_yticklabels(), visible=False)
ax.append(plt.subplot(gs_outer[1]))
plt.show()
I'd now like to get for the left part a row-wise labeling like this:
I tried to add another GridSpec into the GridSpec, but that did not work out:
import pylab as plt
import numpy as np
fig = plt.figure()
gs_outer = plt.GridSpec(1, 2, width_ratios=(10, 1))
gs_medium = plt.matplotlib.gridspec.GridSpecFromSubplotSpec(3, 1, gs_outer[0])
ax_title0 = plt.subplot(gs_medium[0])
ax_title0.set_title('Test!')
gs_row1 = plt.matplotlib.gridspec.GridSpecFromSubplotSpec(1, 3, gs_medium[0])
ax00 = plt.subplot(gs_row1[0]) # toggle this line to see the effect
plt.show()
Adding the ax00 = plt.subplot... line seems to erase the previously created axis
Following CT Zhu comment I came up with the following answer (I don't really like it, but it seems to work)
import pylab as plt
import numpy as np
fig = plt.figure()
rows = 2
cols = 3
row_fraction = 9
row_size = row_fraction / float(rows)
gs_outer = plt.GridSpec(1,2, width_ratios=(9,1))
gs_plots= plt.matplotlib.gridspec.GridSpecFromSubplotSpec(rows * 2, cols, subplot_spec=gs_outer[0], height_ratios = rows * [1, row_size])
# Create title_axes
title_ax = []
for ta in xrange(rows):
row_index = (ta) * 2
title_ax.append(plt.subplot(gs_plots[row_index, :]))
# Create Data axes
ax = []
for row in xrange(rows):
row_index = (row + 1) * 2 -1
for col in xrange(cols):
try:
ax.append(plt.subplot(gs_plots[row_index, col], sharex=ax[0], sharey=ax[0]))
except IndexError:
if row == 0 and col == 0:
ax.append(plt.subplot(gs_plots[row_index, col]))
else:
raise IndexError
# Delete Boxes and Markers from title axes
for ta in title_ax:
ta._frameon = False
ta.xaxis.set_visible(False)
ta.yaxis.set_visible(False)
# Add labels to title axes:
for ta, label in zip(title_ax, ['Row 1', 'Row 2']):
plt.sca(ta)
plt.text(
0.5, 0.5, label, horizontalalignment='center', verticalalignment='center')
# Add common colorbar
gs_cb = plt.matplotlib.gridspec.GridSpecFromSubplotSpec(
1, 1, subplot_spec=gs_outer[1])
ax.append(plt.subplot(gs_cb[:, :]))
Of course labeling and ticklabels could be improved. But how to achive that is likely already explained on SO.
Let's define an example grid pltgrid:
pltgrid = gridspec.GridSpec(ncols=3, nrows=2,
width_ratios=[1]*3, wspace=0.3,
hspace=0.6, height_ratios=[1]*2)
Before your for loop, you can define a list ax using map:
num=list(range(7))
ax=list(map(lambda x : 'ax'+str(x), num))
You may have a list plotnames containing the names. As an example, I'll plot a normal distribution Q-Q plot for each i in the for loop:
for i in xrange(6):
ax[i]=fig.add.subplot(pltgrid[i])
res = stats.probplot(x, dist="norm", plot=ax[i])
# set title for subplot using existing 'plotnames' list
ax[i].set_title(plotnames[i])
# display subplot
ax[i]

Making iterative subplots in one subplot2grid

I would like to have a window that is divided in 4 sectors: in the (0,0) a imshow image (ax1); (1,0) a subplot image that uses twinx() image that divides the window(ax2 & ax3); (1,1) a regular plot image (ax4); and an iterative section (0,1) of plots that should give "number_of_subplots" plots one above the other (ax5). Hopefully with no xticklabels but the last one.
This is how the frame should look like before the iterative subplot creation.
My problem: when iterating to create the subplots on the top right space of the window, the subplots span away from that space and eliminate the ax4
This is how the window looks after the "for" cyle for the subplot creation
Below you'll find a simplification of the code I am using, just so you can see it better. I have replaced my experimental data with random numbers so you can replicate this easily.
Could you give me a hint on what am I doing wrong? I still do not dominate all the handlers in python. I used to do similar things in matlab a few years ago.
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec
import numpy as np
import pdb
pos = [1,2,3,4,5]
N = 50
x = np.random.rand(N)
y = np.random.rand(N)
xx = np.linspace(0, 20, 1000)
fig1 = plt.figure()
number_of_subplots = len(pos) #number between 1-7
ax1 = plt.subplot2grid((number_of_subplots+1,2),(0,0),rowspan = number_of_subplots-1) # Here the idea is to "dinamically" create the division of the grid, making space at the bottom of it for the image in the bottom left.
ax1.scatter(x,y)
ax2 = plt.subplot2grid((number_of_subplots+1,2),(number_of_subplots-1,0), rowspan = 2)
ax2.plot(xx,np.sin(xx),label = 'sin(x)',color = 'b')
ax3 = ax2.twinx()
ax3.plot(xx,np.cos(xx), label = 'cos(x)', color = 'r')
ax4 = plt.subplot2grid((number_of_subplots+1,2),(number_of_subplots-1,1), rowspan = 2)
ax4.plot(xx,np.tan(xx), label = 'tan(x)', color = 'g')
for i,v in enumerate(xrange(number_of_subplots)):
v = v+1
ax5 = plt.subplot2grid((number_of_subplots+1,2),(v-1,1))
ax5.plot(np.sin(xx+3.1416*v/2)) # Grafica los perfiles, asociandoles el mismo color que para los cortes en la imagen 2D
if (i % 2 == 0): #Even
ax5.yaxis.tick_left()
else:
ax5.yaxis.tick_right()
plt.draw()
plt.show()
Solved the issue by using GridSpec as it is supposed to be used. Below is the implementation of the code that gives the following solution.
This is the correct way the image should look like and the implementation is below on the code.
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import numpy as np
import pdb
pos = [1,2,3,4,5]
N = 50
x = np.random.rand(N)
y = np.random.rand(N)
xx = np.linspace(0, 20, 1000)
number_of_subplots = len(pos) #number between 1-7
fig1 = plt.figure()
gs0 = gridspec.GridSpec(2,2,height_ratios=[3,1],hspace=0.1)
ax1 = plt.subplot(gs0[0,0])
ax2 = plt.subplot(gs0[-1,0])
ax4 = plt.subplot(gs0[-1,-1])
gs2 = gridspec.GridSpecFromSubplotSpec(number_of_subplots, 1, subplot_spec=gs0[1],wspace=0.0, hspace=0.0)
ax1.scatter(x,y)
ax2.plot(xx,np.sin(xx),label = 'sin(x)',color = 'b')
ax3 = ax2.twinx()
ax3.plot(xx,np.cos(xx), label = 'cos(x)', color = 'r')
ax4.plot(xx,np.tan(xx), label = 'tan(x)', color = 'g')
for i in enumerate(xrange(number_of_subplots)):
ax5 = plt.subplot(gs2[i,:])
ax5.plot(np.sin(xx+3.1416*i/2))
if (i % 2 == 0): #Even
ax5.yaxis.tick_left()
else:
ax5.yaxis.tick_right()
plt.draw()
plt.show()

Categories

Resources