J'ai accès à numpy et scipy et je souhaite créer une simple FFT d'un jeu de données. J'ai deux listes l'une qui est des valeurs y et l'autre est des horodatages pour ces valeurs y.
Quel est le moyen le plus simple d’alimenter ces listes dans une méthode scipy ou numpy et de tracer la FFT résultante?
J'ai recherché des exemples, mais ils reposent tous sur la création d'un ensemble de fausses données comportant un certain nombre de points de données, une fréquence, etc. .
J'ai essayé l'exemple suivant:
from scipy.fftpack import fft
# Number of samplepoints
N = 600
# sample spacing
T = 1.0 / 800.0
x = np.linspace(0.0, N*T, N)
y = np.sin(50.0 * 2.0*np.pi*x) + 0.5*np.sin(80.0 * 2.0*np.pi*x)
yf = fft(y)
xf = np.linspace(0.0, 1.0/(2.0*T), N/2)
import matplotlib.pyplot as plt
plt.plot(xf, 2.0/N * np.abs(yf[0:N/2]))
plt.grid()
plt.show()
Mais quand je change l'argument de fft en mon ensemble de données et que je l'intrigue, j'obtiens des résultats extrêmement étranges, il semble que la mise à l'échelle de la fréquence soit peut-être désactivée. je ne suis pas sûr.
Voici une Pastebin des données que je tente de FFT
http://Pastebin.com/0WhjjMkbhttp://Pastebin.com/ksM4FvZS
Quand je fais un fft sur le tout, il a juste une énorme pointe à zéro et rien d'autre
Voici mon code:
## Perform FFT WITH SCIPY
signalFFT = fft(yInterp)
## Get Power Spectral Density
signalPSD = np.abs(signalFFT) ** 2
## Get frequencies corresponding to signal PSD
fftFreq = fftfreq(len(signalPSD), spacing)
## Get positive half of frequencies
i = fftfreq>0
##
plt.figurefigsize=(8,4));
plt.plot(fftFreq[i], 10*np.log10(signalPSD[i]));
#plt.xlim(0, 100);
plt.xlabel('Frequency Hz');
plt.ylabel('PSD (dB)')
l'espacement est juste égal à xInterp[1]-xInterp[0]
Je lance donc une forme de votre code fonctionnellement équivalente dans un cahier IPython:
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
import scipy.fftpack
# Number of samplepoints
N = 600
# sample spacing
T = 1.0 / 800.0
x = np.linspace(0.0, N*T, N)
y = np.sin(50.0 * 2.0*np.pi*x) + 0.5*np.sin(80.0 * 2.0*np.pi*x)
yf = scipy.fftpack.fft(y)
xf = np.linspace(0.0, 1.0/(2.0*T), N/2)
fig, ax = plt.subplots()
ax.plot(xf, 2.0/N * np.abs(yf[:N//2]))
plt.show()
Je reçois ce que je crois être une sortie très raisonnable.
Cela fait plus longtemps que je ne tiens à l'admettre depuis que j'étais à l'école d'ingénieur en train de penser au traitement du signal, mais les pics à 50 et 80 sont exactement ce à quoi je m'attendais. Alors, quel est le problème?
Le problème ici est que vous n'avez pas de données périodiques. Vous devez toujours inspecter les données que vous introduisez dans l'algorithme any pour vous assurer qu'il est approprié.
import pandas
import matplotlib.pyplot as plt
#import seaborn
%matplotlib inline
# the OP's data
x = pandas.read_csv('http://Pastebin.com/raw.php?i=ksM4FvZS', skiprows=2, header=None).values
y = pandas.read_csv('http://Pastebin.com/raw.php?i=0WhjjMkb', skiprows=2, header=None).values
fig, ax = plt.subplots()
ax.plot(x, y)
La chose importante à propos de fft est qu’elle ne peut être appliquée qu’aux données dans lesquelles l’horodatage est uniforme (i.e. échantillonnage uniforme dans le temps, comme ce que vous avez montré ci-dessus).
Si l'échantillonnage n'est pas uniforme, veuillez utiliser une fonction pour ajuster les données. Il existe plusieurs tutoriels et fonctions parmi lesquels choisir:
https://github.com/tiagopereira/python_tips/wiki/Scipy%3A-curve-fittinghttp://docs.scipy.org/doc/numpy/reference/generated/numpy.polyfit.html
Si l'ajustement n'est pas une option, vous pouvez directement utiliser une forme d'interpolation pour interpoler les données sur un échantillonnage uniforme:
https://docs.scipy.org/doc/scipy-0.14.0/reference/tutorial/interpolate.html
Lorsque vous avez des échantillons uniformes, il vous suffit de vous préoccuper du delta temporel (t[1] - t[0]
) de vos échantillons. Dans ce cas, vous pouvez directement utiliser les fonctions fft
Y = numpy.fft.fft(y)
freq = numpy.fft.fftfreq(len(y), t[1] - t[0])
pylab.figure()
pylab.plot( freq, numpy.abs(Y) )
pylab.figure()
pylab.plot(freq, numpy.angle(Y) )
pylab.show()
Cela devrait résoudre votre problème.
Le pic élevé que vous avez est dû à la partie DC (non variable, c'est-à-dire freq = 0) de votre signal. C'est une question d'échelle. Si vous souhaitez voir un contenu de fréquence non continu, vous devrez peut-être tracer à partir du décalage 1 et non du décalage 0 de la FFT du signal.
Modification de l'exemple donné ci-dessus par @PaulH
import numpy as np
import matplotlib.pyplot as plt
import scipy.fftpack
# Number of samplepoints
N = 600
# sample spacing
T = 1.0 / 800.0
x = np.linspace(0.0, N*T, N)
y = 10 + np.sin(50.0 * 2.0*np.pi*x) + 0.5*np.sin(80.0 * 2.0*np.pi*x)
yf = scipy.fftpack.fft(y)
xf = np.linspace(0.0, 1.0/(2.0*T), N/2)
plt.subplot(2, 1, 1)
plt.plot(xf, 2.0/N * np.abs(yf[0:N/2]))
plt.subplot(2, 1, 2)
plt.plot(xf[1:], 2.0/N * np.abs(yf[0:N/2])[1:])
Les graphiques de sortie:
Une autre méthode consiste à visualiser les données à l’échelle du journal:
En utilisant:
plt.semilogy(xf, 2.0/N * np.abs(yf[0:N/2]))
Montrera:
Juste pour compléter les réponses déjà données, je voudrais souligner qu’il est souvent important de jouer avec la taille des bacs pour la FFT. Il serait judicieux de tester un ensemble de valeurs et de choisir celle qui convient le mieux à votre application. Souvent, il s'agit du même nombre d'échantillons. Cela a été supposé par la plupart des réponses données et produit des résultats excellents et raisonnables. Au cas où on voudrait explorer cela, voici ma version de code:
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
import scipy.fftpack
fig = plt.figure(figsize=[14,4])
N = 600 # Number of samplepoints
Fs = 800.0
T = 1.0 / Fs # N_samps*T (#samples x sample period) is the sample spacing.
N_fft = 80 # Number of bins (chooses granularity)
x = np.linspace(0, N*T, N) # the interval
y = np.sin(50.0 * 2.0*np.pi*x) + 0.5*np.sin(80.0 * 2.0*np.pi*x) # the signal
# removing the mean of the signal
mean_removed = np.ones_like(y)*np.mean(y)
y = y - mean_removed
# Compute the fft.
yf = scipy.fftpack.fft(y,n=N_fft)
xf = np.arange(0,Fs,Fs/N_fft)
##### Plot the fft #####
ax = plt.subplot(121)
pt, = ax.plot(xf,np.abs(yf), lw=2.0, c='b')
p = plt.Rectangle((Fs/2, 0), Fs/2, ax.get_ylim()[1], facecolor="grey", fill=True, alpha=0.75, hatch="/", zorder=3)
ax.add_patch(p)
ax.set_xlim((ax.get_xlim()[0],Fs))
ax.set_title('FFT', fontsize= 16, fontweight="bold")
ax.set_ylabel('FFT magnitude (power)')
ax.set_xlabel('Frequency (Hz)')
plt.legend((p,), ('mirrowed',))
ax.grid()
##### Close up on the graph of fft#######
# This is the same histogram above, but truncated at the max frequence + an offset.
offset = 1 # just to help the visualization. Nothing important.
ax2 = fig.add_subplot(122)
ax2.plot(xf,np.abs(yf), lw=2.0, c='b')
ax2.set_xticks(xf)
ax2.set_xlim(-1,int(Fs/6)+offset)
ax2.set_title('FFT close-up', fontsize= 16, fontweight="bold")
ax2.set_ylabel('FFT magnitude (power) - log')
ax2.set_xlabel('Frequency (Hz)')
ax2.hold(True)
ax2.grid()
plt.yscale('log')
Il existe déjà d'excellentes solutions sur cette page, mais tous ont supposé que l'ensemble de données était échantillonné/distribué de manière uniforme/uniforme. Je vais essayer de donner un exemple plus général de données échantillonnées au hasard. Je vais aussi utiliser ce tutoriel MATLAB comme exemple:
Ajout des modules requis:
import numpy as np
import matplotlib.pyplot as plt
import scipy.fftpack
import scipy.signal
Générer des échantillons de données:
N = 600 # number of samples
t = np.random.uniform(0.0, 1.0, N) # assuming the time start is 0.0 and time end is 1.0
S = 1.0 * np.sin(50.0 * 2 * np.pi * t) + 0.5 * np.sin(80.0 * 2 * np.pi * t)
X = S + 0.01 * np.random.randn(N) # adding noise
Tri du jeu de données:
order = np.argsort(t)
ts = np.array(t)[order]
Xs = np.array(X)[order]
Rééchantillonnage:
T = (t.max() - t.min()) / N # average period
Fs = 1 / T # average sample rate frequency
f = Fs * np.arange(0, N // 2 + 1) / N; # resampled frequency vector
X_new, t_new = scipy.signal.resample(Xs, N, ts)
représentation graphique des données et des données rééchantillonnées:
plt.xlim(0, 0.1)
plt.plot(t_new, X_new, label="resampled")
plt.plot(ts, Xs, label="org")
plt.legend()
plt.ylabel("X")
plt.xlabel("t")
calculons maintenant le fft:
Y = scipy.fftpack.fft(X_new)
P2 = np.abs(Y / N)
P1 = P2[0 : N // 2 + 1]
P1[1 : -2] = 2 * P1[1 : -2]
plt.ylabel("Y")
plt.xlabel("f")
plt.plot(f, P1)
Je dois avoir construit une fonction qui traite de tracer FFT de vrais signaux. Dans ma fonction, il y a l'amplitude RÉELLE du signal (encore une fois, à cause de l'hypothèse du signal réel, ce qui signifie symétrie ...):
import matplotlib.pyplot as plt
import numpy as np
import warnings
def fftPlot(sig, dt=None, block=False, plot=True):
# here it's assumes analytic signal (real signal...)- so only half of the axis is required
if dt is None:
dt = 1
t = np.arange(0, sig.shape[-1])
xLabel = 'samples'
else:
t = np.arange(0, sig.shape[-1]) * dt
xLabel = 'freq [Hz]'
if sig.shape[0] % 2 != 0:
warnings.warn("signal prefered to be even in size, autoFixing it...")
t = t[0:-1]
sig = sig[0:-1]
sigFFT = np.fft.fft(sig) / t.shape[0] # divided by size t for coherent magnitude
freq = np.fft.fftfreq(t.shape[0], d=dt)
# plot analytic signal - right half of freq axis needed only...
firstNegInd = np.argmax(freq < 0)
freqAxisPos = freq[0:firstNegInd]
sigFFTPos = 2 * sigFFT[0:firstNegInd] # *2 because of magnitude of analytic signal
if plot:
plt.figure()
plt.plot(freqAxisPos, np.abs(sigFFTPos))
plt.xlabel(xLabel)
plt.ylabel('mag')
plt.title('Analytic FFT plot')
plt.show(block=block)
return sigFFTPos, freqAxisPos
if __== "__main__":
dt = 1 / 1000
f0 = 1 / dt / 4
t = np.arange(0, 1 + dt, dt)
sig = np.sin(2 * np.pi * f0 * t)
fftPlot(sig, dt=dt)
fftPlot(sig)
t = np.arange(0, 1 + dt, dt)
sig = np.sin(2 * np.pi * f0 * t) + 10 * np.sin(2 * np.pi * f0 / 2 * t)
fftPlot(sig, dt=dt, block=True)