title and axis gets over each other - python

I am trying to plot several plots. I want to add to each a title. However, in my code the title and the axis gets over each other. Is there a workaround this?
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
randn = np.random.randn
fig = plt.figure(figsize=(15, 12))
train= df = pd.DataFrame(randn(10, 34))
for i in range(1, train.shape[1]):
plt.subplot(6, 6, i)
f = plt.gca()
f.axes.get_yaxis().set_visible(False)
f.set_title(train.columns.values[i])
vals = np.size(train.iloc[:, i].unique())
if vals < 10:
bins = vals
else:
vals = 10
plt.hist(train.iloc[:, i], bins=30, color='#3F5D7D')
plt.show()

you could try:
plt.tight_layout()

The solution is:
plt.tight_layout()
Here is some good documentation, it has an example that looks like your problem.
http://matplotlib.org/users/tight_layout_guide.html

An alternative solution would be to place your subplots manually in your figure to allow a maximum of flexibility in your layout design. I've put together some code that shows how this can be done. Note that there is a big part of the code that is only to make the xticks format visually appealing.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
plt.close('all')
#------------------------------------------------------------- prepare data ----
randn = np.random.randn
train= df = pd.DataFrame(randn(10, 34))
ngraphs = train.shape[1]
#------------------------------------------------------------ create figure ----
fwidth = 15
fheight = 12
fig = plt.figure(figsize=(fwidth, fheight))
fig.patch.set_facecolor('white')
left_margin = 0.5 / fwidth
right_margin = 0.5 / fwidth
bottom_margin = 0.5 / fheight
top_margin = 0.75 / fheight
vinter_margin = 0.75 / fheight
hinter_margin = 0.5 / fwidth
#-------------------------------------------------------------- create axes ----
ncol = 6
nrow = int(np.ceil(ngraphs/float(ncol)))
w0 = (1 - (left_margin + right_margin + (ncol-1) * hinter_margin)) / ncol
h0 = (1 - (bottom_margin + top_margin + (nrow-1) * vinter_margin)) / nrow
AX0 = [0] * ngraphs
itot = 0
y0 = 1 - top_margin - h0
for row in range(nrow):
x0 = left_margin
for col in range(ncol):
AX0[itot] = fig.add_axes([x0, y0, w0, h0], frameon=True)
#-------------------------------------------------------- plot data ----
vals = np.size(train.iloc[:, itot].unique())
if vals < 10:
bins = vals
else:
vals = 10
AX0[itot].hist(train.iloc[:, itot], bins=30, color='#3F5D7D')
#--------------------------------------------------------- set axis ----
AX0[itot].axes.get_yaxis().set_visible(False)
AX0[itot].set_title(train.columns.values[itot])
#---- major ticks ----
AX0[itot].tick_params(top='off', labeltop='off')
AX0[itot].tick_params(axis='x', direction='out', labelsize=8)
trainmax = np.ceil(np.max(train.iloc[:, itot])/0.5)*0.5
trainmin = np.floor(np.min(train.iloc[:, itot])/0.5)*0.5
AX0[itot].set_xticks([trainmin,0, trainmax])
#---- minor ticks ----
AX0[itot].set_xticks(np.arange(trainmin, trainmax, 0.5), minor=True)
AX0[itot].tick_params(axis='x', which='minor', direction='out',
top='off', length=3)
#---- axis limits ----
AX0[itot].axis(xmin=trainmin, xmax=trainmax)
#---------------------------------------------------------- iterate ----
x0 = x0 + w0 + hinter_margin
itot += 1
if itot == ngraphs:
break
y0 = y0 - h0 - vinter_margin
plt.show(block=False)
fig.savefig('subplot_layout.png')
Which results in:

Related

How to generate an animation composed of several plots in python?

I'm having trouble plotting an animation with python. What I want to do is basically an animation that contains the superposition of many complete plots. In such a way that each frame will be a plot given by
plt.plot(r, Sevol[n])
The code and error displayed on the screen are below. Thanks for any help.
UserWarning: Animation was deleted without rendering anything. This is most likely not intended. To prevent deletion, assign the Animation to a variable, e.g. anim, that exists until you have outputted the Animation using plt.show() or anim.save().
import numpy as np
import math
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
nr = 300
ri = 0
rf = 300
dr = (rf - ri) / (nr - 1)
nt = 1000
dt = 0.1
r = np.linspace(ri, rf, num=nr)
def rk3_step(t, h, y, f, *args):
k1 = h * f(t , y , *args)
k2 = h * f(t + h/2, y + 1/2 * k1 , *args)
k3 = h * f(t + h , y - k1 + 2 * k2, *args)
return y + 1/6*(k1 + 4*k2 + k3)
def rhs_perturb(t, u):
S = u.T[0]
S_dot = u.T[1]
F = u.T[2]
F_dot = u.T[3]
rhs = np.empty((nr, 4))
rhs[0] = np.array([S_dot[0],
(S[2] - 2 * S[1] + S[0]) / (dr ** 2), # + F[0],
F_dot[0],
- S[0] + (F[2] - 2 * F[1] + F[0]) / (dr ** 2)])
rhs[-1] = np.array([S_dot[-1],
(S[-1] - 2 * S[-2] + S[-3]) / (dr ** 2), # + F[-1],
F_dot[-1],
- S[-1] + (F[-1] - 2 * F[-2] + F[-3]) / (dr ** 2)])
for i in range(1, nr - 1):
rhs[i] = np.array([S_dot[i],
(S[i + 1] - 2 * S[i] + S[i - 1]) / (dr ** 2), # + F[i],
F_dot[i],
- S[i] + (F[i + 1] - 2 * F[i] + F[i - 1]) / (dr ** 2)])
return rhs
sigma = 3
r0 = 100
F = np.empty(nr)
F_dot = np.empty(nr)
S = np.empty(nr)
S_dot = np.empty(nr)
for i in range(nr):
F[i] = 0
F_dot[i] = 0
S_dot[i] = 0
S[i] = math.exp(-(r[i] - r0)**2 / sigma**2)
uin = np.block([[S], [S_dot], [F], [F_dot]]).T
u = np.copy(uin)
uaux = np.copy(uin)
nsave = 10
Sevol = np.empty((math.floor(nt/nsave),nr))
Sevol[0] = S
Fevol = np.empty((math.floor(nt/nsave),nr))
Fevol[0] = F
for n in range(nt):
uaux = rk3_step(n * dt, dt, u, rhs_perturb)
if np.any(np.isnan(uaux)):
break
u = uaux
if (n + 1) % nsave == 0:
Sevol[math.floor(n / nsave)] = u.T[0]
Fevol[math.floor(n / nsave)] = u.T[2]
fig = plt.figure()
plt.xlabel('r')
plt.xlabel('S')
plt.grid()
plt.xlim(ri, rf)
def animate(i):
numPlots = i //10 # how many plots (-1) will be shown based on the frame.
for n in range(numPlots):
plt.plot(r[n], Sevol[n], color='gold', markersize=3)
ani = FuncAnimation(fig, animate, frames=100, interval=10, blit = False, repeat = False)
plt.close()
plt.show()
I would suggest initializing your animations with a list of empty placeholders plots. In your case, it looks like you need 100 plots. And then update the data for plot i at each frame with the actual values.
Below is what the animation code looks like:
import numpy as np
import math
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation, PillowWriter
N_plots=100
fig,ax = plt.subplots()
color = plt.cm.viridis(np.linspace(0, 1, N_plots))
lns=[ax.plot([],[],color=color[i]) for i in range(N_plots)]
lns=[lns[i][0] for i in range(N_plots)]
plt.xlabel('r')
plt.ylabel('S')
plt.grid()
plt.xlim(ri, rf)
plt.ylim(-0.25, 1)
def animate(i):
lns[i].set_data(r,Sevol[i])
return lns
ani = FuncAnimation(fig, animate, frames=N_plots, interval=100)
And the output gives:
EDIT:
Note that if instead of superimposing the curves you just want to replace them by the next, you can simply use set_data to update your one plot.
See code below:
import numpy as np
import math
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation, PillowWriter
N_plots=100
fig,ax = plt.subplots()
color = plt.cm.viridis(np.linspace(0, 1, N_plots))
ln,=ax.plot([],[])
plt.xlabel('r')
plt.ylabel('S')
plt.grid()
plt.xlim(ri, rf)
plt.ylim(-0.25, 1)
def animate(i):
ln.set_data(r,Sevol[i])
ln.set_color(color[i])
return ln
ani = FuncAnimation(fig, animate, frames=N_plots, interval=100)
And the output gives:

Making fading trails in python FuncAnimation

I have the following code that uses 4th order Runge Kutta to solve the equations of motion of a double pendulum. I am attempting to animate the positions of the two pendulums using FuncAnimation. I tried removing the 0th element of each coordinate list after every 10th new element is plotted. I thought this would create a fading trail effect. Instead it just plotted one point. (note x1 and y1 are the coordinates of the first pendulum, and x2 and y2 are the coordinates of the second coupled pendulum) I feel like I'm over complicating things. when I remove the if i % 10 ==1: loop the code animates fine, but it does not delete any previous points. I feel this should be fairly easy to do but I could not find any straight forward examples, and the documentation was not extremely helpful.
In short I have 4 lists of coordinates that describe the position of two objects. I want to animate the current position of these two objects and not any previous positions.
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.animation as animation
from matplotlib.animation import FuncAnimation
from itertools import count
from IPython import display
h=.001
t=0
l1=1
l2=1
m1=1
m2=1
theta1= 0
theta2 = np.pi/2
thetadot1=0
thetadot2=0
g=9.8
x1=[]
y1=[]
x2=[]
y2=[]
E=[]
T=[]
Theta_dot1=[]
Theta_dot2=[]
kinetic_energy=[]
potential_energy=[]
while(t<4):
KE = .5*m1*((thetadot1*l1*np.cos(theta1))**2+(thetadot1*l1*np.sin(theta1))**2) + .5*m2*((thetadot1*l1*np.cos(theta1)+thetadot2*l2*np.cos(theta2))**2+(thetadot1*l1*np.sin(theta1)+thetadot2*l2*np.sin(theta2))**2)
PE = (m1*g*(l1-l1*np.cos(theta1))+m2*g*(l1+l2-l1*np.cos(theta1)-l2*np.cos(theta2)))
E.append(KE+PE)
kinetic_energy.append(KE)
potential_energy.append(PE)
Theta_dot1.append(thetadot1)
Theta_dot2.append(thetadot2)
T.append(t)
x1.append(l1*np.sin(theta1))
y1.append(-l1*np.cos(theta1))
x2.append(l1*np.sin(theta1)+l2*np.sin(theta2))
y2.append(-l1*np.cos(theta1)-l2*np.cos(theta2))
P1 = (-g*(2*m1+m2)*np.sin(theta1)-m2*g*np.sin(theta1-2*theta2)-2*np.sin(theta1-theta2)*m2*(l2*thetadot2**2+l1*thetadot1**2*(np.cos(theta1-theta2))))/(l1*(2*m1+m2*(1-np.cos(2*(theta1-theta2)))))
L1 = (2*np.sin(theta1-theta2)*(l1*thetadot1**2*(m1+m2)+np.cos(theta1)*g*(m1+m2)+l2*m2*thetadot2**2*np.cos(theta1-theta2)) )/(l2*(2*m1+m2*(1-np.cos(2*(theta1-theta2)))))
G1 = thetadot1
H1 = thetadot2
thetadot1_1 = thetadot1 + (h/2)*P1
thetadot2_1 = thetadot2 + (h/2)*L1
theta1_1 = theta1 + (h/2)*G1
theta2_1 = theta2 + (h/2)*H1
P2 = (-g*(2*m1+m2)*np.sin(theta1_1)-m2*g*np.sin(theta1_1-2*theta2_1)-2*np.sin(theta1_1-theta2_1)*m2*(l2*thetadot2_1**2+l1*thetadot1_1**2*(np.cos(theta1_1-theta2_1))))/(l1*(2*m1+m2*(1-np.cos(2*(theta1_1-theta2_1)))))
L2 = (2*np.sin(theta1_1-theta2_1)*(l1*thetadot1_1**2*(m1+m2)+np.cos(theta1_1)*g*(m1+m2)+l2*m2*thetadot2_1**2*np.cos(theta1_1-theta2_1)) )/(l2*(2*m1+m2*(1-np.cos(2*(theta1_1-theta2_1)))))
G2 = thetadot1_1
H2 = thetadot2_1
thetadot1_2 = thetadot1_1 + (h/2)*P2
thetadot2_2 = thetadot2_1 + (h/2)*L2
theta1_2 = theta1_1 + (h/2)*G2
theta2_2 = theta2_1 + (h/2)*H2
P3 = (-g*(2*m1+m2)*np.sin(theta1_2)-m2*g*np.sin(theta1_2-2*theta2_2)-2*np.sin(theta1_2-theta2_2)*m2*(l2*thetadot2_2**2+l1*thetadot1_2**2*(np.cos(theta1_2-theta2_2))))/(l1*(2*m1+m2*(1-np.cos(2*(theta1_2-theta2_2)))))
L3 = (2*np.sin(theta1_2-theta2_2)*(l1*thetadot1_2**2*(m1+m2)+np.cos(theta1_2)*g*(m1+m2)+l2*m2*thetadot2_2**2*np.cos(theta1_2-theta2_2)) )/(l2*(2*m1+m2*(1-np.cos(2*(theta1_2-theta2_2)))))
G3 = thetadot1_2
H3 = thetadot2_2
thetadot1_3 = thetadot1_2 + (h/2)*P3
thetadot2_3 = thetadot2_2 + (h/2)*L3
theta1_3 = theta1_2 + (h/2)*G3
theta2_3 = theta2_2 + (h/2)*H3
P4 = (-g*(2*m1+m2)*np.sin(theta1_3)-m2*g*np.sin(theta1_3-2*theta2_3)-2*np.sin(theta1_3-theta2_3)*m2*(l2*thetadot2_3**2+l1*thetadot1_3**2*(np.cos(theta1_3-theta2_3))))/(l1*(2*m1+m2*(1-np.cos(2*(theta1_3-theta2_3)))))
L4 = (2*np.sin(theta1_3-theta2_3)*(l1*thetadot1_3**2*(m1+m2)+np.cos(theta1_3)*g*(m1+m2)+l2*m2*thetadot2_3**2*np.cos(theta1_3-theta2_3)) )/(l2*(2*m1+m2*(1-np.cos(2*(theta1_3-theta2_3)))))
G4 = thetadot1_3
H4 = thetadot2_3
thetadot1 = thetadot1 + (h/6.0) * (P1+(2.*P2)+(2.0*P3) + P4)
thetadot2 = thetadot2 + (h/6.0) * (L1+(2.*L2)+(2.0*L3) + L4)
theta1 = theta1 + (h/6.0) * (G1+(2.*G2)+(2.0*G3) + G4)
theta2 = theta2 + (h/6.0) * (H1+(2.*H2)+(2.0*H3) + H4)
t=t+h
fig, axes = plt.subplots(nrows = 1, ncols = 1, figsize = (15,5))
axes.set_ylim(-2.5, 2.5)
axes.set_xlim(-2.5, 2.5)
plt.style.use("ggplot")
x_1,y_1,x_2,y_2 = [], [], [], []
def animate(i):
if i % 10 ==1: #this is the stuff that is not working
x_1.remove(0)
y_1.remove(0)
x_2.remove(0)
y_2.remove(0)
x_1.append(x1[i*10])
y_1.append(y1[i*10])
x_2.append(x2[i*10])
y_2.append(y2[i*10])
axes.plot(x_1,y_1,'.', color="red")
axes.plot(x_2,y_2,'.', color="gray", linewidth=0.5)
anim = FuncAnimation(fig, animate, interval=.1)
If you want fading lines you have to deal with the extra complexity of LineCollection, which is not easy to master.
import matplotlib.colors as colors
from matplotlib.collections import LineCollection
from matplotlib.lines import Line2D
n_points_to_render = 500
# opacity of the segments
alphas = np.linspace(0, 1, n_points_to_render)
# create "solid" color maps with a varying opacity
red = colors.to_rgb("red") + (0.0,)
redfade = colors.LinearSegmentedColormap.from_list('my', [red, "red"])
green = colors.to_rgb("green") + (0.0,)
greenfade = colors.LinearSegmentedColormap.from_list('my', [green, "green"])
def get_segments(i):
# LineCollection requires segments
_x1 = x1[i:i+n_points_to_render]
_y1 = y1[i:i+n_points_to_render]
_x2 = x2[i:i+n_points_to_render]
_y2 = y2[i:i+n_points_to_render]
points1 = np.vstack((_x1, _y1)).T.reshape(-1, 1, 2)
segments1 = np.hstack((points1[:-1], points1[1:]))
points2 = np.vstack((_x2, _y2)).T.reshape(-1, 1, 2)
segments2 = np.hstack((points2[:-1], points2[1:]))
return segments1, segments2
fig, ax = plt.subplots(nrows = 1, ncols = 1)
ax.set_ylim(-2.5, 2.5)
ax.set_xlim(-2.5, 2.5)
plt.style.use("ggplot")
# create and add two LineCollections
segments1, segments2 = get_segments(0)
lc1 = LineCollection(segments1, array=alphas, cmap=redfade, lw=2)
lc2 = LineCollection(segments2, array=alphas, cmap=greenfade, lw=2)
line1 = ax.add_collection(lc1)
line2 = ax.add_collection(lc2)
def animate(i):
segments1, segments2 = get_segments(i)
line1.set_segments(segments1)
line2.set_segments(segments2)
# create a legend as LineCollection doesn't have any by default
l1_legend = Line2D([0, 1], [0, 1], color="r", linewidth=2)
l2_legend = Line2D([0, 1], [0, 1], color="g", linewidth=2)
ax.legend([l1_legend, l2_legend], ['Line 1', 'Line 2'])
anim = FuncAnimation(fig, animate, frames=len(x1) - n_points_to_render, interval=30)
plt.show()

Plot confidence interval of a duration series

I measured the duration of 6000 requests.
I got now an Array of 6000 elements. Each element represents the duration of a connection request in milliseconds.
[3,2,2,3,4,2,2,4,2,3,3,4,2,4,4,3,3,3,4,3,2,3,5,5,2,4,4,2,2,2,3,5,3,2,2,3,3,3,5,4........]
I want to plot the confidence interval in Python and in a clearly arranged manner.
Do you have any Idea how I should plot them?
From what I understood this code should answer your question
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
import seaborn as sns
from statistics import NormalDist
X = np.random.sample(100)
data = ((X - min(X)) / (max(X) - min(X))) * 3 + 3
confidence_interval = 0.95
def getCI(data, ci):
normalDist = NormalDist.from_samples(data)
z = NormalDist().inv_cdf((1 + ci) / 2.)
p = normalDist.stdev * z / ((len(data) - 1) ** .5)
return normalDist.mean, normalDist.mean - p, normalDist.mean + p
avg, lower, upper = getCI(data, confidence_interval)
sns.set_style("whitegrid")
plt.figure(figsize=(8, 4))
sns.histplot(data, bins = 10)
plt.axvspan(lower, upper, facecolor='r', alpha=0.2)
plt.axvline(avg, color = 'b', label = 'Average')
plt.ylabel("Operations")
plt.xlabel("Connection Request Duration (ms)")
plt.show()
For boxplot:
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
import seaborn as sns
from statistics import NormalDist
X = np.random.sample(100)
data = ((X - min(X)) / (max(X) - min(X))) * 3 + 3
confidence_interval = 0.95
def getCI(data, ci):
normalDist = NormalDist.from_samples(data)
z = NormalDist().inv_cdf((1 + ci) / 2.)
p = normalDist.stdev * z / ((len(data) - 1) ** .5)
return normalDist.mean, normalDist.mean - p, normalDist.mean + p
avg, lower, upper = getCI(data, confidence_interval)
sns.set_style("whitegrid")
plt.figure(figsize=(8, 4))
sns.boxplot(data = data, orient = "h")
plt.axvspan(lower, upper, facecolor='r', alpha=0.4)
plt.axvline(avg, color = 'b', label = 'Average')
plt.ylabel("Operations")
plt.xlabel("Connection Request Duration (ms)")
plt.yticks([0],["Server Retry Request Delay"])
plt.savefig("fig.png")
plt.show()
For Multiple Plots:
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
import seaborn as sns
from statistics import NormalDist
X1, X2 = np.random.sample(100), np.random.sample(100)
data1, data2 = ((X1 - min(X1)) / (max(X1) - min(X1))) * 3 + 3, ((X2 - min(X2)) / (max(X2) - min(X2))) * 2 + 3
confidence_interval = 0.95
def getCI(data, ci):
normalDist = NormalDist.from_samples(data)
z = NormalDist().inv_cdf((1 + ci) / 2.)
p = normalDist.stdev * z / ((len(data) - 1) ** .5)
return normalDist.mean, normalDist.mean - p, normalDist.mean + p
sns.set_style("whitegrid")
avg1, lower1, upper1 = getCI(data1, confidence_interval)
avg2, lower2, upper2 = getCI(data2, confidence_interval)
fig = plt.figure(figsize=(12, 6))
ax1 = fig.add_subplot(211)
ax2 = fig.add_subplot(212, sharex = ax1, sharey = ax1)
sns.boxplot(data = data1, orient = "h", ax = ax1)
ax1.axvspan(lower1, upper1, facecolor='r', alpha=0.4)
ax1.axvline(avg1, color = 'b', label = 'Average')
sns.boxplot(data = data2, orient = "h", ax = ax2)
ax2.axvspan(lower2, upper2, facecolor='r', alpha=0.4)
ax2.axvline(avg2, color = 'b', label = 'Average')
ax2.set_xlabel("Connection Request Duration (ms)")
plt.setp(ax1.get_xticklabels(), visible=False)
plt.setp(ax1.get_yticklabels(), visible=False)
plt.setp(ax2.get_yticklabels(), visible=False)
fig.text(0.08, 0.5, "Operations", va='center', rotation='vertical')
plt.show()

How to (re)scale the x-axis to fit certain points in the graph?

I want to rescale my (qualitative) x-axis so the two peaks (visible in the graph) correlate to their actual values (i. e. 500 keV and 1274 MeV).
How can I do this?
import numpy as np
import matplotlib.pyplot as plt
def read_from_file(filename):
return np.loadtxt(filename)
data = list(read_from_file("calibration.txt"))
print(data.index(max(data[:2000])))#x value 500kev
print(data.index(max(data[2000:])))#x value 1274
fig = plt.figure()
ax = fig.add_subplot(111)
x = range(len(data))
plt.plot(x, data)
plt.xlim(0, 5000)
plt.ylim(0, 7000)
plt.title("$^{22}$Na Spectrum")
plt.xlabel("Energy")
plt.ylabel("Amount of Photons")
plt.grid()
ax.annotate("500 keV", xy = (1450, 6541), xytext = (1600, 6500))
ax.annotate("1274 MeV", xy = (3500, 950), xytext = (3700, 1100))
plt.show()
Using numpy, you can find the index of the two spikes (i.e. no need to convert the data to a list) using argmax.
Then, you can scale the x values using:
xnew = val1 + (x - max1) / (max2 - max1) * (val2 - val1)
where val1 and val2 are the values of your peaks, and max1 and max2 are the indices of those peaks.
Here's a bit of code that should work:
import numpy as np
import matplotlib.pyplot as plt
# Fake some data approximately in your range. You can ignore this bit!
# Random numbers for noise
data = 1000. + np.random.rand(5000) * 100.
x = np.arange(len(data))
# Add the first spike
mu1, sd1 = 1450., 300.
pdf1 = (1./(sd1*2.*np.pi) * np.exp(-(x - mu1)**2 / sd1**2)) * 1e7
data += pdf1
# Add the second spike
mu2, sd2 = 3500., 200.
pdf2 = (1./(sd2*2.*np.pi) * np.exp(-(x - mu2)**2 / sd2**2)) * 1e6
data += pdf2
# End of fake data generation
# Find the index of the first maximum (using your '2000' cutoff)
cutoff = 2000
max1 = float(np.argmax(data[:cutoff]))
# Find the index of the second cutoff
max2 = float(np.argmax(data[cutoff:]) + cutoff)
# The actual values of the two spikes
val1, val2 = 500., 1274
# Scale the xvalues
xnew = val1 + (x - max1) / (max2 - max1) * (val2 - val1)
# Plot
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(xnew, data)
ax.set_ylim(0, 7000)
ax.set_title("$^{22}$Na Spectrum")
ax.set_xlabel("Energy")
ax.set_ylabel("Number of Photons")
ax.grid()
# Add some lines at the actual spikes to check scaling worked
ax.axvline(val1)
ax.axvline(val2)
plt.show()
Funny you should ask this question. I am currently trying to push an example into MatPlotLib that shows exactly how to do this. You can view the recipe here: https://github.com/madphysicist/matplotlib/blob/7b05223c85741120019b81e1248c20f9bc090c61/examples/ticks_and_spines/tick_transform_formatter.py
You do not need the entire code in the example (or the tick formatter that uses it) but the mapping function will help you create the scaled x-array (also, use np.argmax instead of index(max(...)):
ind500 = np.argmaxmax(data[:2000]))
ind1274 = np.argmax(data[2000:])) + 2000
x_scaled = (x - ind500) * (1274 - 500) / (ind1274 - ind500) + 500
You can use x_scaled to plot as usual:
plt.plot(x_scaled, data)
...
Combining it all together (and making a couple of tweaks to use OO API instead of pyplot):
import numpy as np
from matplotlib import pyplot as plt
data = np.loadtxt("calibration.txt") # Don't convert this back to a list
ind500 = np.argmaxmax(data[:2000]))
ind1274 = np.argmax(data[2000:])) + 2000
x = (np.arange(len(data)) - ind500) * (1274 - 500) / (ind1274 - ind500) + 500
fig, ax = plt.subplots()
ax.plot(x, data)
plt.title("$^{22}$Na Spectrum")
plt.xlabel("Energy")
plt.ylabel("Photons Counts")
plt.grid()
ax.annotate("500 keV", xy = (500, data[ind500]), xytext = (550, data[ind500] + 100))
ax.annotate("1274 keV", xy = (1274, data[ind1274]), xytext = (1324, data[ind1274] + 100))
plt.show()
The example I linked to would allow you to display the x-axis in entirely different units without actually modifying your x-array.

How can I plot a spectrogram of a signal by computing the power spectrum on binned windows?

Here I can generate a signal:
import numpy as np
from matplotlib import pyplot as plt
from numpy.lib import stride_tricks
import seaborn as sns
sns.set(style = "darkgrid" )
fs = 48000.0
t = np.arange(0, 10, 1.0/fs) # 0 to 10 sec at 48k samples per second
f0 = 1000
phi = np.pi/2 # pi/2
x = 0 # initial x
f = [500, 100, 40, 1] #vector of frequencies
A = [1, 0.5, 0.25, 0.1] #vector of amplitudes
for i in range(0, len(f)):
x = x + A[i] * np.sin(2 * np.pi * f[i] * t + phi) #add waves
x = x + max(x) # shift plot upwards
plt.plot(t, x)
plt.axis([0, .05, 0, max(x)])
plt.xlabel('time')
plt.ylabel('amplitude')
plt.show()
Here I can plot the power spectrum of the entire signal:
time_step = 1/fs
ps = np.abs(np.fft.fft(x))**2
freqs = np.fft.fftfreq(x.size, time_step)
idx = np.argsort(freqs)
plt.plot(freqs[idx], 256*ps[idx]/max(ps[idx])) # set max to 256 for later image plotting purposes
plt.xlabel('frequency')
plt.ylabel('power')
plt.show()
Next I want to generate a spectrogram, represented as an image of frequency (y-axis) and time (x-axis), but I am new to fourier analysis and am confused about how to use a window function (rectangular, hamming, hanning, etc) during this stage. Is there a proper way to do this so that a window function of my choosing can be used to break up the signal in time?
add this:
M = 5000
overlap = 500
unique = M - overlap
han = np.hanning(M)
f_border = 2*max(f)
for i in range(0, x.shape[0], unique):
if i + M > x.shape[0]:
break
curr_x = x[i:i+M]
y = 10*np.log10(np.abs(np.fft.fft(curr_x*han))**2)
if i == 0:
freqs = np.fft.fftfreq(curr_x.size, time_step)
idx = np.argsort(freqs)
freqs = freqs[idx]
idx2 = np.where(np.logical_and(freqs > 0, freqs < f_border))[0]
y = y[idx][idx2][np.newaxis].T
try:
stereogram = np.hstack([stereogram, y])
except NameError:
stereogram = y
fig = plt.figure()
ax = fig.add_subplot(111)
ax.imshow(stereogram)
yticks = ax.get_yticks()[1:-1]
plt.yticks(yticks, (yticks * f_border/yticks[-1]).astype('str'))
plt.ylabel('frequency')
plt.xlabel('time')
plt.show()
or you can use matplotlib.pyplot.specgram see: http://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.specgram

Categories

Resources