Why does matplotlib magnitude_spectrum function seem to show wrong magnitudes? - python-3.x

I created a 1 second long audio sample consiting of two sine waves and then used matplotlibs magnitude spectrum function to plot the spectrum and the results seem to be wrong. The two waves have the exact same amplitude throughout the one second audio sample, and yet the magnitudes are vastly different. This seemed weird to me, so I have also used numpys functions to plot the DFT and the magnitudes are the exact same, as I think they should be. The resulting plots are shown in the image below. Does anyone know, why that might be? Did I do anything wrong in my code? Any help will be much appriciated.
Minimal working example:
import matplotlib.pyplot as plt
import numpy as np
sr = 20000
freq1 = 200
freq2 = 100
duration = 1
x = np.linspace(0, duration, sr * duration)
y = np.concatenate([0.5*np.sin(freq1 * 2 * np.pi * x[:10000]) + 0.5*np.sin(freq2 * 2 * np.pi * x[:10000]), np.sin(freq1 * 2 * np.pi * x[10000:15000]), np.sin(freq2 * 2 * np.pi * x[15000:20000])])
fig, ax = plt.subplots(3, 1, figsize=(12, 10))
ax[0].plot(x, y)
ax[0].axis(xmin=0, xmax=1)
ax[0].set_xlabel('Time [s]')
ax[0].set_ylabel('Amplitude [-]')
ax[1].magnitude_spectrum(y, Fs=sr, color='C1')
ax[1].axis(xmin=0, xmax=500)
ax[1].set_xlabel('Frequency [Hz]')
ax[1].set_ylabel('Magnitude [-]')
ax[2].plot(np.fft.rfftfreq(sr, d=1/sr), np.abs(np.fft.rfft(y, norm='ortho'))/100)
ax[2].axis(xmin=0, xmax=500)
ax[2].set_xlabel('Frequency [Hz]')
ax[2].set_ylabel('Magnitude [-]')
plt.tight_layout()
plt.show()

I think it is related to the window used in the matplotlib. By default, it uses Hanning window, so change to window type to window_none. Also the way the scaling is done is different in both cases. By doing following changes, you will see them both match.
from matplotlib import mlab
ax[1].magnitude_spectrum(y, Fs=sr, color='C1', window=mlab.window_none)
ax[2].plot(np.fft.rfftfreq(sr, d=1/sr), np.abs(np.fft.rfft(y))/sr)
results in

Related

Converting Normal Distribution to Lognormal distribution

I have been following lectures of MIT open course on Application of Mathematics in Finance. I am trying to code out the concepts for better understanding.
According to lectures(from what I understand), if random variable X is normally distributed then exp(X) is log-normally distributed and vice versa (please correct me if I am wrong here).
Here is what I tried:
I have list of integers that are normally distributed:
import numpy as np
import matplotlib.pyplot as plt
from math import sqrt
X = np.array(l)
mu = np.mean(X)
sigma = np.std(X)
count, bins, ignored = plt.hist(X, 35, density=True)
plt.plot(bins, 1/(sigma * np.sqrt(2 * np.pi)) * np.exp( - (bins - mu)**2 / (2 * sigma**2)
),linewidth=2, color='r')
plt.show()
Output:
Normally distributed curve
Now I want to get log-normal distribution from above data, here is what I have tried:
import numpy as np
import matplotlib.pyplot as plt
from math import sqrt
X = np.array(l)
ln = []
for x in X:
val = np.e**x
ln.append(val)
X_ln = np.array(ln)
X_ln = np.array(X_ln) / np.min(X_ln)
mu = np.mean(X_ln)
sigma = np.std(X_ln)
count, bins, ignored = plt.hist(X_ln, 10, density=True)
x = np.linspace(min(bins), max(bins), 10000)
pdf = (np.exp(-(np.log(x) - mu)**2 / (2 * sigma**2)) / (x * sigma * np.sqrt(2 * np.pi)))
plt.plot(x, pdf, color='r', linewidth=2)
plt.show()
Output :
Not so clean Output
I know there is a better way to do this, but I can't figure out how. Any suggestions would be highly appreciated.
Here are couple of references:
Log normal distribution in Python
MIT lecture notes(topic-1.1)
In case this is relevant, here is a list of elements I am trying to process:
List of elements
Update 1:
I have normalized X before adding values to ln. This fixed the distribution of histogram, however, I can't seem to fix to get red line to show log-normal distribution. Also the new histogram distribution is not very different from normal distribution. I can't think of any suitable reason for that.
This is the block of code I have added:
def normalize(v):
norm=np.linalg.norm(v, ord=1)
if norm==0:
norm=np.finfo(v.dtype).eps
return v/norm
X = np.array(l)
X = normalize(X)
New Output:
Slightly better result

plotting a 3d-vector field with colors in dependence of the z component of the vectors

This is my first question and I hope I can describe my issue properly.
I tried to write down a minimal example. My goal is to get a nice plot of a vector field in the xy plane (so just one layer, but a 3d view) where the colors of my arrows should be completely red (blue) if they are pointing completely in the positive (negative) z-direction and gray if they are located in the xy plane. (Slightly red resp. red if they have some positive resp. negative z component etc - so I thought about a 'coolwarm' colormap. But I do not really know how to tho this. I tried to solve my problem with this question and the answers Adding colors to a 3d quiver plot in matplotlib and with the way I am adding my color bars to pcolormesh-plots where it is working fine.
, but I didn't really manage to do it properly, as you can see here:
plot obtained from my code
I do not really understand some of the code they used there and it would be nice if someone could help me with that :)
I do not understand what this part does q.set_array(np.linspace(-1,1,3)) and why I need q.set_edgecolor(c) and q.set_facecolor(c).
Besides I am
from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.colors import BoundaryNorm
from matplotlib.ticker import MaxNLocator
# Make the grid
x, y, z = np.meshgrid(np.arange(-0.8, 1, 0.2),
np.arange(-0.8, 1, 0.2),
np.arange(0.0, 0.6, 0.5))
# Make the direction data for the arrows
u = np.sin(np.pi * x) * np.cos(np.pi * y) * np.cos(np.pi * z)
v = -np.cos(np.pi * x) * np.sin(np.pi * y) * np.cos(np.pi * z)
w = 0.2 + np.sqrt(2.0 / 3.0) * np.cos(np.pi * x) * np.cos(np.pi * y) * np.sin(np.pi * z)
#define colorbar like I usually do it for 2d density plot etc where it works
cmap = 'coolwarm'
cm = plt.get_cmap(cmap)
plot_min = -1.
plot_max = 1.
levels = MaxNLocator(nbins=100).tick_values(plot_min, plot_max)
norm = BoundaryNorm(levels, ncolors=cm.N, clip=True)
# Color by z-component of vectors (u,v,w) angle
c = w
# Flatten and normalize
c = (c.ravel() - c.min()) / c.ptp()
# Repeat for each body line and two head lines
c = np.concatenate((c, np.repeat(c, 2)))
# Colormap
c = getattr(plt.cm, cmap)(c)
fig = plt.figure(figsize=(10,7))
ax = fig.gca(projection='3d')
q = ax.quiver(x, y, z, u, v, w, colors=c, cmap = cmap, length=0.1, normalize=norm)
q.set_array(np.linspace(-1,1,3))
cbar = fig.colorbar(q, ticks=[-1, 0, 1], fraction=0.015)
cbar.ax.set_yticklabels(['-1', '0', '1'])
cbar.ax.tick_params(labelsize=15)
q.set_edgecolor(c)
q.set_facecolor(c)
#ax.set_zlim(-0.4, 0.4)
ax.view_init(azim=90, elev=20)
ax.grid(False)
plt.axis('off')
plt.show()
if this would work, it would be super cool!
Is there a way to make the arrows look nicer? It would be perfect if the arrows could look like the ones in Mathematica-plots like this:
example from Mathematica
Thank you a lot in advance!
"Tube" Arrows in Python
I found this awesome post. It was exactly the way I want my arrows to look like in the end :)

Why does librosa STFT show wrong frequencies?

I generated a 200 Hz sine wave using numpy and then used librosas stft() and specshow() functions to show a spectrogram. However, the frequency it is showing is not 200 Hz. When I use matplotlibs magnitude_spectrum() function, it shows exactly 200 Hz. Does anyone know why that might be? Am I doing something wrong? Any help will be much appriciated.
The results from librosas spectrogram and matplotlibs frequency spectrum can be seen in the image below.
Minimal working example:
import matplotlib.pyplot as plt
from matplotlib import mlab
%matplotlib inline
import numpy as np
import librosa
import librosa.display
sr = 20000
freq1 = 200
n_fft=2000
x = np.linspace(0, 1, sr)
y = 0.5*np.sin(freq1 * 2 * np.pi * x)
no_window = np.linspace(1, 1, n_fft)
D = np.abs(librosa.stft(y, n_fft=n_fft, hop_length=int(n_fft/2), window=no_window, center=False,))
plt.figure(figsize=(9, 4))
librosa.display.specshow(D, y_axis='linear')
plt.xlabel('Time [s]')
plt.ylabel('Frequency [Hz]')
plt.ylim(0, 250)
plt.tight_layout()
plt.show()
plt.figure(figsize=(9, 4))
plt.magnitude_spectrum(y, Fs=sr, color='C1', window=mlab.window_none)
plt.xlim(0, 250)
plt.xlabel('Frequency [Hz]')
plt.ylabel('Amplitude [-]')
plt.tight_layout()
plt.show()
Just passing the results to specshow is not enough. You also need to tell it what scale these results are on. You do this be passing the sample rate parameter sr like this:
librosa.display.specshow(D, y_axis='linear', sr=sr)
If you don't, it defaults to sr=22050, hop_length=512, which is certainly not correct in your case.
This is similar to the answer given here.

Python - Graphing normal distribution line with list of data

I'm working on the Electrical Engineering project which requires plotting the normal distribution of the list of data.
We randomly measured the resistance of 30 resistors and wrote down them.
X = [14.95, 14.94, 14.92, 14.98, 16.53, 14.96, 16.20, 14.32, 15.32, 14.25, 15.36, 14.95, 15.13, 14.26, 14.94, 15.6,
15.20, 14.94, 15.02, 15, 14.62, 14.94, 14.94, 14.98, 15.12, 15.06, 14.95, 14.96, 15.13, 15.20]
I want to get graph like this:
But I get the graph like this one:
I have to get more values in the graph where datum is near to mean.
This is the code that I'm using currently:
import numpy as np
from matplotlib import pyplot as plt
import math
X = [14.95, 14.94, 14.92, 14.98, 16.53, 14.96, 16.20, 14.32, 15.32, 14.25, 15.36, 14.95, 15.13, 14.26, 14.94, 15.6,
15.20, 14.94, 15.02, 15, 14.62, 14.94, 14.94, 14.98, 15.12, 15.06, 14.95, 14.96, 15.13, 15.20]
X = np.sort(X)
mean = np.mean(X)
sigma = 0
for i in X:
sigma += np.square(mean - i)
sigma = np.sqrt(sigma / (len(X) - 1))
def func(x):
return np.exp(np.square(x - mean) / (2 * np.square(sigma))) / np.sqrt(2 * math.pi * sigma)
Y = []
for i in X:
Y.append(func(i))
plt.plot(X, Y, marker='o', color='b')
plt.show()
Assuming I understood your question properly, which I think that you are just trying to add more data points to generate a normal distribution curve.
mu = np.mean(X)
sigma = np.std(X) #You manually calculated it but you can also use this built-in function
data = np.random.normal(mu, sigma, SIZE_OF_DATA_YOU_NEED)
However, if you're also just trying to form the normal distribution curve, you can't just be plotting each value against its probability density function.
Try
count, bins, ignored = plt.hist(data, 30, normed=True)
plt.plot(bins, 1/(sigma * np.sqrt(2 * np.pi)) * np.exp( - (bins - mu)**2 / (2 * sigma**2) ),linewidth=2, color='r')
plt.show()
Might want to concatenate X against the new data points too.
Hope this help in some way, also attaching a link to numpy.random.normal() in case it helps in some kind of way (https://docs.scipy.org/doc/numpy-1.10.1/reference/generated/numpy.random.normal.html).

Using horizontal line to fit the model

I am writing a python code using horizontal line for investigating the under-fiting using the function sin(2.pi.x) in range of [0,1].
I first generate N data points by adding some random noise using Gaussian distribution with mu=0 and sigma=1.
import matplotlib.pyplot as plt
import numpy as np
# generate N random points
N=30
X= np.random.rand(N,1)
y= np.sin(np.pi*2*X)+ np.random.randn(N,1)
I need to fit the model using horizontal line and display it. But I don't know how to do next.
Could you help me figure out this problem? I'd appreciate about it.
Assuming that you want to use the least squares loss function, by definition you are trying to find the value of yhat minimizing np.sum((y-yhat)**2). Differentiating by yhat, you'll find that the minimum is achieved at yhat = np.sum(y)/N, which is of course nothing but y.mean(), as also already pointed out by #ImportanceOfBeingErnest in the comments.
plt.scatter(X, y)
plt.plot(X, np.zeros(N) + np.mean(y))
From what I understand you're generating a noisy Sine wave and trying to fit a horizontal line?
import os
import fnmatch
import numpy as np
import matplotlib.pyplot as plt
# generate N random points
N=60
X= np.linspace(0.0,2*np.pi, num=N)
noise = 0.1 * np.random.randn(N)
y= np.sin(4*X) + noise
numer = sum([xi*yi for xi,yi in zip(X, y)]) - N * np.mean(X) * np.mean(y)
denum = sum([xi**2 for xi in X]) - N * np.mean(X)**2
b = numer / denum
A = np.mean(y) - b * np.mean(X)
y_ = b * X+ A
plt.plot(X,y)
plt.plot(X,y_)
plt.show()

Resources