Subplot and share y-axis in a 4-by-n grid - python

I'm trying to define a function that can plot subplots within a 4-by-n grid. The code below works but I want to make the subplots share y axis, which I think I (definitely?) have to use the method mentioned in this post. However, because I'm doing a 4-by-n grid and I'm doing a loop, assigning a name to each subplot seems troublesome to me. Any ideas? Thank you in advance!
Currently my function looks like this:
def make_plots(df, name_list):
n = len(name_list)
j = 1
plt.figure(1, dpi=500, figsize=(10,10/4*n/4))
for i in name_list:
plt.subplot(n/4,4,j)
plt.title('Table {}: {}'.format(j,i), size='xx-small')
plt.grid(False)
a = [i for i in list(df[i]) if i>0]
plt.hist(a,bins=7,rwidth=0.7)
plt.axvline(x=np.mean(a), color='red')
j+=1
plt.tight_layout(pad=0.2,h_pad=0.2, w_pad=0.2)
plt.show()

You could create the figure and subplots first and use the sharey = True argument. Then use zip to iterate over both name_list and the axes array at the same time:
def make_plots(df, name_list):
n = len(name_list)
j = 1
fig, axes = plt.subplots(n/4, 4, sharey=True, figsize=(10, 10/4*n/4))
for i, ax in zip(name_list, axes.flatten()):
ax.set_title('Table {}: {}'.format(j,i), size='xx-small')
ax.grid(False)
a = [i for i in list(df[i]) if i>0]
ax.hist(a,bins=7,rwidth=0.7)
ax.axvline(x=np.mean(a), color='red')
j+=1
plt.tight_layout(pad=0.2,h_pad=0.2, w_pad=0.2)
plt.show()

Related

Matplotlib subplot object being classified/recoqnized as numpy array. Hence not able to use twinx() function and etc

So I'm trying to plot two plots on the same graph, one Y axis on either sides sharing the same X axis. I've done this earlier and hence knew how to do it (or I though so). Anyways now I was trying to implement it under a function since I need to make a lot of plots and hence wanted a more modular solution.
Now when trying to run the same thing under a fucntion it throws error 'numpy.ndarray' object has no attribute 'twinx'. This is because for some reason ax1 is being shown of class numpy.ndarray which should actually be matplotlib.axes._subplots.AxesSubplot.
Please Help.
def pumped_up_plotting(data, colname1, colname2):
fig, ax1 = plt.subplots(13, 4, figsize=(5*4, 5*13))
print("Look here lil bitch: ", type(ax1))
ax2 = ax1.twinx()
plt.subplots_adjust(hspace=0.3)
for i in range(0, 13):
for j in range(0,4):
id_ = profiles[i*4+j]
samp = data[data["profile_id"] == id_]
ax1[i, j].plot(samp["time"], samp[colname1], label=colname1, color="blue")
ax2[i, j].plot(samp["time"], samp[colname2], label=colname2, color="yellow")
ax1[i, j].set_xlabel("Time")
lines_1, labels_1 = ax1.get_legend_handles_labels()
lines_2, labels_2 = ax2.get_legend_handles_labels()
lines = lines_1 + lines_2
labels = labels_1 + labels_2
ax1[i, j].legend(lines, labels, loc=0)
pumped_up_plotting(df, "rotor", "motor_work")
Screenshot of error message
ax1 is a numpy array because you are creating a grid of 13 by 4 axes instead of a single axes. You do this by supplying 13 and 4 as the first two arguments to plt.subplots. I'm not sure what you intended those numbers to do, but if you delete them, it should work.
As of now, ax1 is a numpy array with 13 columns an 4 rows containing the individual axis objects.
I'll try to explain what went wrong. To simplify, I'm gonna use a 2 x 2 grid instead of 13 x 4. So fig, ax1 = plt.subplots(2, 2).
Then, ax1 will look like this:
array([[<AxesSubplot:>, <AxesSubplot:>],
[<AxesSubplot:>, <AxesSubplot:>]], dtype=object)
If you try to call ax1.twinx(), it won't work because ax1 is actually not an axis, but the array containing all your 4 axes of the grid.
So what you would have to call if you wanted to create a twin axis of the first axis, would be ax1[0,0].twinx(). Since you want to do it for every axis and not just the first one, you can do it inside a nested loop where you loop over the rows and columns of the numpy array. Since you are already doing this, you can justput that line inside your already existing loop.
This looks like that line.
ax2 = ax1[i, j].twinx()
Here, we are taking the individual axis object from the numpy array by indexing (as you were already doing before) and calling twinx on it. This returns a twin axis which we are saving as ax2. Note that this is kind of confusing, since ax2 is a single axis object while ax1 is an array containg axis objects. I personally would rename ax1 to axs so it's clear this variable contains multiple axes.
Because ax2 is already a single axis object, we can call the plotting functions directly on it, and don't have to index it.
def pumped_up_plotting(data, colname1, colname2):
fig, ax1 = plt.subplots(13, 4, figsize=(5*4, 5*13))
print("Type of ax1 ", type(ax1))
plt.subplots_adjust(hspace=0.3)
for i in range(0, 13):
for j in range(0,4):
ax2 = ax1[i, j].twinx()
id_ = profiles[i*4+j]
samp = data[data["profile_id"] == id_]
ax1[i, j].plot(samp["time"], samp[colname1], label=colname1, color="blue")
ax2.plot(samp["time"], samp[colname2], label=colname2, color="yellow")
ax1[i, j].set_xlabel("Time")
lines_1, labels_1 = ax1.get_legend_handles_labels()
lines_2, labels_2 = ax2.get_legend_handles_labels()
lines = lines_1 + lines_2
labels = labels_1 + labels_2
ax1[i, j].legend(lines, labels, loc=0)
pumped_up_plotting(df, "rotor", "motor_work")
My way of doing this more clearly would be:
def pumped_up_plotting(data, colname1, colname2):
fig, axs = plt.subplots(13, 4, figsize=(5*4, 5*13))
plt.subplots_adjust(hspace=0.3)
for i, row in enumerate(axs):
for j, ax in enumerate(row):
ax2 = ax.twinx()
id_ = profiles[i*4+j]
samp = data[data["profile_id"] == id_]
ax.plot(samp["time"], samp[colname1], label=colname1, color="blue")
ax2.plot(samp["time"], samp[colname2], label=colname2, color="yellow")
ax.set_xlabel("Time")
lines_1, labels_1 = ax.get_legend_handles_labels()
lines_2, labels_2 = ax2.get_legend_handles_labels()
lines = lines_1 + lines_2
labels = labels_1 + labels_2
ax.legend(lines, labels, loc=0)
pumped_up_plotting(df, "rotor", "motor_work")

Plotting energy levels in stacks, on top of one another

I have a code that gives me different energy levels. The code and the output is shown here:
from numpy import*
from matplotlib.pyplot import*
N=[0,1,2,3,4]
s=0.5
hw=1
def Energy(n):
if n%2==0:
if n==0:
j=0.5
E=[(n+1.5)*hw-0.1*(j-0.5)*0.5-0.0225*(j+0.5)*(j-0.5)]
return(E)
else:
l=list(range(0,n+2,2))
j1=[abs(l+s) for l in l ]
j2=[l-s for l in l]
j2=list(filter(lambda x:x>0,j2))
E1=[(n+1.5)*hw-0.1*(j-0.5)*0.5-0.0225*(j+0.5)*(j-0.5) for j in j1]
E2=[(n+1.5)*hw+0.1*(j+0.5)*0.5-0.0225*(j+0.5)*(j-0.5) for j in j2]
return(E1+E2)
else:
l=list(range(1,n+2,2))
j1=[abs(l+s) for l in l]
j2=[abs(l-s) for l in l]
E1=[(n+1.5)*hw-0.1*(j-0.5)*0.5-0.0225*(j+0.5)*(j-0.5) for j in j1]
E2=[(n+1.5)*hw+0.1*(j+0.5)*0.5-0.0225*(j+0.5)*(j-0.5) for j in j2]
return(E1+E2)
E=[]
for n in N:
E.extend(Energy(n))
E.sort()
print(E)
orbital=[r'$1s_{1/2}$',r'$1p_{3/2}$',r'$1p_{1/2}$',r'$1d_{5/2}$',r'$2s_{1/2}$',r'$1d_{3/2}$',r'$1f_{7/2}$',r'$2p_{3/2}$',r'$1f_{5/2}$',r'$2p_{1/2}$',r'$1g_{9/2}$',r'$2d_{5/2}$',r'$1g_{7/2}$',r'$3s_{1/2}$',r'$2d_{3/2}$']
x = arange(len(E))
fig, ax =subplots()
ax.scatter(x, E, s=900, marker="_", linewidth=2, zorder=3)
ax.grid(axis='y')
for xi,yi,tx in zip(x,E,orbital):
ax.annotate(tx, xy=(xi,yi), xytext=(7,-3), size=5,
ha="center",va='top', textcoords="offset points")
ax.margins(0.1)
ylabel('energy >>')
title('Nuclear Energy levels')
The output is a graph containing the energy levels but spread out parallel to the x axis
What I actually need is the levels to not be spread across. I want them in a stack, one on the top of another. I tried modifying this code for that, but to no avail. Can someone help with this?
Instead of
x = arange(len(E))
(just before fig, ax =subplots()) use
x = [1] * len(E)
to have the same x-coordinate for all your levels:
You will probably want to increase the parameter s=, too, in your ax.scatter() method, for example to 90000:
ax.scatter(x, E, s=90000, marker="_", linewidth=2, zorder=3)
and change position of annotations — slightly change parameters xy=, xytext= in your code
for xi,yi,tx in zip(x,E,orbital):
ax.annotate(tx, xy=(xi,yi), xytext=(7,-3), size=5,
ha="center",va='top', textcoords="offset points")
to (for example):
for xi, yi, tx in zip(x, E, orbital):
ax.annotate(tx, xy=(.65*xi, yi), xytext=(7, 3), size=5,
ha="center", va='top', textcoords="offset points")
and change the overall image size to increase gaps between levels — in your
fig, ax = subplots()
use the figsize= parameter:
fig, ax = subplots(figsize=(6.5, 12))
Finally, you may remove ticks from x-axis and add minor ticks to y-axis:
import matplotlib as mpl # this line is better to put near the start of your code
ax.set_xticks([])
ax.yaxis.set_minor_locator(mpl.ticker.MaxNLocator(50))

two (or more) graphs in one plot with different x-axis AND y-axis scales in python

I want 3 graphs on one axes object, for example:
#example x- and y-data
x_values1=[1,2,3,4,5]
y_values1=[1,2,3,4,5]
x_values2=[-1000,-800,-600,-400,-200]
y_values2=[10,20,39,40,50]
x_values3=[150,200,250,300,350]
y_values3=[10,20,30,40,50]
#make axes
fig=plt.figure()
ax=fig.add_subplot(111)
now I want to add all three data sets to ax. But they shouldn't share any x- or y-axis (since then because of the diffenrent scales one would be way smaller thant the other. I need something like ax.twinx(), ax.twiny(), but both the x- and y-axis need to be independent.
I want to do this, because I want to put the two attached plots (and a third one, that is similar to the second one) in one plot ("put them on top of each other").
Plot1
Plot2
I then would put the x/y-labels (and/or ticks, limits) of the second plot on the right/top and the x/y-limits of another plot in the bottom/left. I dont need x/y-labels of the 3. plot.
How do I do this?
The idea would be to create three subplots at the same position. In order to make sure, they will be recognized as different plots, their properties need to differ - and the easiest way to achieve this is simply to provide a different label, ax=fig.add_subplot(111, label="1").
The rest is simply adjusting all the axes parameters, such that the resulting plot looks appealing.
It's a little bit of work to set all the parameters, but the following should do what you need.
import matplotlib.pyplot as plt
x_values1=[1,2,3,4,5]
y_values1=[1,2,2,4,1]
x_values2=[-1000,-800,-600,-400,-200]
y_values2=[10,20,39,40,50]
x_values3=[150,200,250,300,350]
y_values3=[10,20,30,40,50]
fig=plt.figure()
ax=fig.add_subplot(111, label="1")
ax2=fig.add_subplot(111, label="2", frame_on=False)
ax3=fig.add_subplot(111, label="3", frame_on=False)
ax.plot(x_values1, y_values1, color="C0")
ax.set_xlabel("x label 1", color="C0")
ax.set_ylabel("y label 1", color="C0")
ax.tick_params(axis='x', colors="C0")
ax.tick_params(axis='y', colors="C0")
ax2.scatter(x_values2, y_values2, color="C1")
ax2.xaxis.tick_top()
ax2.yaxis.tick_right()
ax2.set_xlabel('x label 2', color="C1")
ax2.set_ylabel('y label 2', color="C1")
ax2.xaxis.set_label_position('top')
ax2.yaxis.set_label_position('right')
ax2.tick_params(axis='x', colors="C1")
ax2.tick_params(axis='y', colors="C1")
ax3.plot(x_values3, y_values3, color="C3")
ax3.set_xticks([])
ax3.set_yticks([])
plt.show()
You could also standardize the data so it shares the same limits and then plot the limits of the desired second scale "manually".
This function standardizes the data to the limits of the first set of points:
def standardize(data):
for a in range(2):
span = max(data[0][a]) - min(data[0][a])
min_ = min(data[0][a])
for idx in range(len(data)):
standardize = (max(data[idx][a]) - min(data[idx][a]))/span
data[idx][a] = [i/standardize + min_ - min([i/standardize
for i in data[idx][a]]) for i in data[idx][a]]
return data
Then, plotting the data is easy:
import matplotlib.pyplot as plt
data = [[[1,2,3,4,5],[1,2,2,4,1]], [[-1000,-800,-600,-400,-200], [10,20,39,40,50]], [[150,200,250,300,350], [10,20,30,40,50]]]
limits = [(min(data[1][a]), max(data[1][a])) for a in range(2)]
norm_data = standardize(data)
fig, ax = plt.subplots()
for x, y in norm_data:
ax.plot(x, y)
ax2, ax3 = ax.twinx(), ax.twiny()
ax2.set_ylim(limits[1])
ax3.set_xlim(limits[0])
plt.show()
Since all data points have the limits of the first set of points, we can just plot them on the same axis. Then, using the limits of the desired second x and y axis we can set the limits for these two.
In this example, you can plot multiple lines in each x-y-axis, and legend each line.
import numpy as np
import matplotlib.pyplot as plt
X1 = np.arange(10)
X1 = np.stack([X1, X1])
Y1 = np.random.randint(1, 10, (2, 10))
X2 = np.arange(0, 1000, 200)
X2 = np.stack([X2, X2])
Y2 = np.random.randint(100, 200, (2, 5))
x_label_names = ['XXX', 'xxx']
y_label_names = ['YYY', 'yyy']
X1_legend_names = ['X1_legend1', 'X1_legend2']
X2_legend_names = ['X2_legend1', 'X2_legend2']
def plot_by_two_xaxis(X1, Y1, X2, Y2, x_label_names: list, y_label_names: list, X1_legend_names: list, X2_legend_names: list):
fig = plt.figure()
ax1s = []
ax2s = []
lines = []
j = 0
for i in range(len(X1)):
j += 1
ax1s.append(fig.add_subplot(111, label=f"{j}", frame_on=(j == 1)))
for i in range(len(X2)):
j += 1
ax2s.append(fig.add_subplot(111, label=f"{j}", frame_on=(j == 1)))
k = 0
for i in range(len(X1)):
lines.append(ax1s[i].plot(X1[i], Y1[i], color=f"C{k}")[0])
if i == 0:
ax1s[i].set_xlabel(x_label_names[0], color=f"C{k}")
ax1s[i].set_ylabel(y_label_names[0], color=f"C{k}")
ax1s[i].tick_params(axis='x', colors=f"C{k}")
ax1s[i].tick_params(axis='y', colors=f"C{k}")
else:
ax1s[i].set_xticks([])
ax1s[i].set_yticks([])
k += 1
for i in range(len(X1)):
lines.append(ax2s[i].plot(X2[i], Y2[i], color=f"C{k}")[0])
if i == 0:
ax2s[i].xaxis.tick_top()
ax2s[i].yaxis.tick_right()
ax2s[i].set_xlabel(x_label_names[1], color=f"C{k}")
ax2s[i].set_ylabel(y_label_names[1], color=f"C{k}")
ax2s[i].xaxis.set_label_position('top')
ax2s[i].yaxis.set_label_position('right')
ax2s[i].tick_params(axis='x', colors=f"C{k}")
ax2s[i].tick_params(axis='y', colors=f"C{k}")
else:
ax2s[i].set_xticks([])
ax2s[i].set_yticks([])
k += 1
ax1s[0].legend(lines, X1_legend_names + X2_legend_names)
plt.show()
plot_by_two_xaxis(X1, Y1, X2, Y2, x_label_names,
y_label_names, X1_legend_names, X2_legend_names)

Looping through a function to plot several subplots, Python

I have variables x and y
def function(a,b):
x = x[(x>a)*(x<b)]
y = y[(y<a)*(y>b)]
# perform some fitting routine using curve_fit on x and y
fig = plt.figure()
ax = fig.add_subplot(111)
phist,xedge,yedge,img = ax.hist2d(x,y,bins=20,norm=LogNorm())
im = ax.imshow(phist,cmap=plt.cm.jet,norm=LogNorm(),aspect='auto')
fig.colorbar(im,ax=ax)
fig.show()
All works fine. But I have 6 pairs of different input parameters a and b. I would like to somehow call function(a,b) using a loop and plot the six different x and y (corresponding to the 6 input pairs) as 6 subplots.
like we do
ax1 = fig.add_subplot(231) # x vs y for a1,b1
ax2 = fig.add_subplot(232) # x vs y for a2,b2
....
ax6 = fig.add_subplot(236) # x vs y for a6,b6
I would like to get an idea of how to proceed to get the final subplot!
I know that it can be done manually by specifying different variables, like x1 and y1 for the first input pair a and b and so on for the other 6 pairs (x2,y2...,x6,y6). But it will be a very lengthy and confusing code.
The key is using the three parameter form of subplot:
import matplotlib.pyplot as plt
# Build a list of pairs for a, b
ab = zip(range(6), range(6))
#iterate through them
for i, (a, b) in enumerate(ab):
plt.subplot(2, 3, i+1)
#function(a, b)
plt.plot(a, b)
plt.show()
You'll just have to take the call to figure out of the function.
Use plt.subplots instead of plt.subplot (note the "s" at the end). fig, axs = plt.subplots(2, 3) will create a figure with 2x3 group of subplots, where fig is the figure, and axs is a 2x3 numpy array where each element is the axis object corresponding to the axis in the same position in the figure (so axs[1, 2] is the bottom-right axis).
You can then either use a pair of loops to loop over each row then each axis in that row:
fig, axs = plt.subplots(2, 3)
for i, row in enumerate(axs):
for j, ax in enumerate(row):
ax.imshow(foo[i, j])
fig.show()
Or you can use ravel to flatten the rows and whatever you want to get the data from:
fig, axs = plt.subplots(2, 3)
foor = foo.ravel()
for i, ax in enumerate(axs.ravel()):
ax.imshow(foor[i])
fig.show()
Note that ravel is a view, not a copy, so this won't take any additional memory.

How to get circles into correct subplot in matplotlib?

I am trying to recreate this https://www.youtube.com/watch?v=LznjC4Lo7lE
I am able to plot the circles I want in matplotlib, but I want to have two subplots next to each other with the circles in the left subplot. I guess I don't understand the ax1,ax2 variables, because the lines wind up in the left subplot, and the circles in the right subplot.
I need them both in the left subplot so I can put the sinusoids in the right subplot. I think my mistake is simple but trying ax1.Circle crashes because Circle is only callable from plt. Any ideas?
Here is the code:
def harmonics( Branches, colors, t, dotsize=2, blur=True, circles=True,save=False, newdir=False, prefix='',path='',note=''):
txt=''
Ds=[]
f, (ax1, ax2) = plt.subplots(1, 2, sharey=True)
# ax1.plot(x, y)
# ax1.set_title('Sharing Y axis')
# ax2.scatter(x, y)
for ct2, B in enumerate(Branches):
D,P,L,K,N = B[0],B[1],B[2],B[3],B[4]
Ds.append(sum([np.absolute(val) for val in D]))
col = colors[ct2]
A = ramify(B,t)
R = [0+0j]
coll=np.array([])
fig = plt.gcf()
KIRKULOS={}
for ct,a in enumerate(A):
for x in range(len(a)):
if x == 0:
ax1.plot([R[ct].real,float(a[x].real)+R[ct].real],[R[ct].imag,float(a[x].imag)+R[ct].imag],col,ms=dotsize+2)#color='#FF4D4D',label='python')
rr = float(a[x].real)+R[ct].real + (float(a[x].imag)+R[ct].imag)*1j
R.append(rr)
else:
pass#plt.plot([R[ct].real,float(a[x].real)+R[ct].real],[R[ct].imag,float(a[x].imag)+R[ct].imag],color='#FFFFFF',ms=dotsize,label='python')
if circles:
KIRKULOS[ct]=plt.Circle((R[ct].real,R[ct].imag),D[ct],color=col[-1], fill=False)
plt.gca().add_artist(KIRKULOS[ct])
coll = np.append(coll,a)
plt.axis("equal")
limit=max(Ds)+max(Ds)*.2
plt.xlim((-limit,limit))
plt.ylim((-limit,limit))
plt.ylabel('iy')
plt.xlabel('x')
for ct,B in enumerate(Branches):# in RAMI.keys():
D,P,L,K,N = B[0],B[1],B[2],B[3],B[4]
txt = txt +'\n\n'+str(D)+'\t Radius'+'\n'+str(P)+'\t Phase'+'\n'+str(L)+'\t Order'+'\n'+str(K)+'\t Frequency'+'\n'+str(N)+'\t Degree'
txt=txt+'\n\n'+note
fig.set_size_inches(10,10)
if save:
if newdir:
import time
import os
now = str(time.time())+'/'
os.makedirs(path+now)
path = path+now
f=open(path+prefix+'.txt','wb+')
f.write(txt)
f.close()
plt.savefig(path+prefix+'.jpg')
plt.show()
Circle just generates the patch, calling it does not add the cirlce to an subplot (axes). This is than done plt.gca().add_artist(KIRKULOS[ct]). gca stands for get current axes and return the active subplot, so replacing it with your ax will do: ax1.add_artist(KIRKULOS[ct])

Categories