web-dev-qa-db-fra.com

Tracé d'une transformation de Fourier rapide en Python

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] 

49
user3123955

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.

enter image description here

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?

En réponse aux données brutes et aux commentaires postés

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)

enter image description here

62
Paul H

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. 

16
ssm

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: Ploting FFT signal with DC and then when removing it (skipping freq = 0)

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:enter image description here

7
hesham_EE

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')

les parcelles de sortie:  enter image description here

4
ewerlopes

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")

 enter image description here 

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)

 enter image description here 

1
Foad

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)
0
asaflotz