Побудова швидкого перетворення Фур'є на Python


96

Я маю доступ до NumPy та SciPy і хочу створити простий ШПФ з набору даних. У мене є два списки, один із яких є yзначеннями, а інший - мітками часу для цих yзначень.

Який найпростіший спосіб подати ці списки в метод SciPy або NumPy та побудувати графік отриманого ШПФ?

Я шукав приклади, але всі вони покладаються на створення набору фальшивих даних з деякою кількістю точок даних, частотою тощо, і насправді не показують, як це зробити лише з набором даних та відповідними мітками часу .

Я спробував наступний приклад:

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

Але коли я міняю аргумент fftна свій набір даних і складаю його, я отримую надзвичайно дивні результати, і, схоже, масштабування частоти може бути вимкнено. Я не впевнений.

Ось пастебін даних, які я намагаюся здійснити на ШПФ

http://pastebin.com/0WhjjMkb http://pastebin.com/ksM4FvZS

Коли я використовую fft()все це, він просто має величезний стрибок на нулі і більше нічого.

Ось мій код:

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

Інтервал просто дорівнює xInterp[1]-xInterp[0].


покажіть нам, що ви пробували, як це не вдалося, і приклади, з яких ви працюєте.
Paul H

Я опублікував приклад, який я спробував, а також те, що я про це думав, я думаю, що я просто збентежений щодо того, як правильно скласти результат.
user3123955

це чудовий приклад, але в чому саме проблема? цей код чудово працює для мене. сюжет просто не з’являється?
Paul H

а саме, які аргументи ви використовуєте (нам потрібно побачити принаймні деякі ваші дані)
Paul H

я додав пастебін осей x та y, дані x знаходяться у секундах, а дані y - це просто зчитування датчика. Коли я
поміщаю

Відповіді:


100

Тож я запускаю функціонально еквівалентну форму вашого коду в блокноті 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()

Я отримую, як я вважаю, дуже розумний результат.

введіть тут опис зображення

Це було довше, ніж я хочу визнати, оскільки я навчався в машинобудівній школі, думаючи про обробку сигналів, але сплески на 50 і 80 - це саме те, на що я би очікував. То в чому проблема?

У відповідь на опубліковані необроблені дані та коментарі

Проблема в тому, що у вас немає періодичних даних. Завжди слід перевіряти дані, які ви вводите в будь-який алгоритм, щоб переконатися, що це доречно.

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)

введіть тут опис зображення


1
Це не те, що приклад помилковий, я не знаю, як це взяти і застосувати до своїх даних.
user3123955

@ user3123955, правильно. саме тому нам потрібно бачити ваші дані та те, як це не вдається, якщо ми збираємося вам допомогти.
Paul H

я додав pastebin
user3123955

2
@ user3123955 так, що ви очікуєте від цього робити будь-який алгоритм ШПФ? вам потрібно очистити свої дані.
Paul H

6
@PaulH НЕ амплітуда при частоті 50 Hzбути 1і на частоті 80 Hzбути 0.5?
Фуркан Хашим

24

Найважливіше у fft полягає в тому, що його можна застосовувати лише до даних, у яких мітка часу є рівномірною ( тобто рівномірний вибірки в часі, як те, що ви показали вище).

У разі нерівномірної вибірки, будь ласка, використовуйте функцію для підбору даних. На вибір є кілька підручників та функцій:

https://github.com/tiagopereira/python_tips/wiki/Scipy%3A-curve-fitting http://docs.scipy.org/doc/numpy/reference/generated/numpy.polyfit.html

Якщо підгонка не є можливою, ви можете безпосередньо використовувати якусь форму інтерполяції для інтерполяції даних до рівномірної вибірки:

https://docs.scipy.org/doc/scipy-0.14.0/reference/tutorial/interpolate.html

Коли у вас є однакові зразки, вам доведеться турбуватися лише про дельту часу ( t[1] - t[0]) ваших зразків. У цьому випадку ви можете безпосередньо використовувати функції 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()

Це має вирішити вашу проблему.


2
Я інтерполював свої дані для рівного інтервалу. Чи можете ви сказати мені, що саме робить fftfreq? навіщо йому потрібна моя вісь х? чому ви складаєте графік абссу Y та кута? Кут є фазою? До чого відноситься фаза? Коли я роблю це зі своїми даними, він просто має гігантський пік на 0 Гц і дуже швидко відключається, але я подаю йому дані, які не мають постійного зміщення (я роблю великий пропускний діапазон для даних з краями від 0,15 Гц до 12 Гц. щоб позбутися постійного зміщення, мої дані в будь-якому випадку не повинні перевищувати 4 Гц, тому смуга повинна змусити мене втратити інформацію).
user3123955

2
1. fftfreqнадає вам частотні компоненти, що відповідають вашим даним. Якщо побудувати графік, freqви побачите, що вісь x не є функцією, яка постійно збільшується. Вам доведеться переконатися, що у вас є правильні частотні компоненти по осі х. Ви можете подивитися керівництво: docs.scipy.org/doc/numpy/reference/generated/…
ssm

2
2. Більшості людей сподобається дивитись на величину і фазу польоту. Одним реченням важко пояснити, що інформація фази вам скаже, але все, що я можу сказати, це те, що це має значення, коли ви поєднуєте сигнали. Коли ви комбінуєте сигнали однієї і тієї ж частоти, які є фазовими, вони посилюються, тоді як коли вони виходять за межі фази на 180 градусів, вони загасають. Це стає важливим, коли ви проектуєте підсилювачі або будь-які елементи, що мають зворотний зв'язок.
ssm

2
3. Як правило, ваша найнижча частота буде мати практично нульову фазу, і це стосується цього. Коли сигнали рухаються по вашій системі, кожна частота рухається з різною швидкістю. Це фазова швидкість. Графік фаз дає вам цю інформацію. Я не знаю, з яким системою ти працюєш, тому не можу дати точної відповіді. З таких питань краще ознайомитись з управлінням зворотним зв’язком, аналоговою електронікою, цифровою обробкою сигналів, теорією електромагнітного поля тощо, або чимось, що є більш специфічним для вашої системи.
ssm

4
4. Замість того щоб використовувати ваші дані, то чому б вам не почати генерувати свої власні сигнали: t = linspace(0, 10, 1000); ys = [ (1.0/i)*sin(i*t) for i in arange(10)]; y = reduce(lambda m, n: m+n, ys). Потім побудуйте графік кожного з ysі загального обсягу yта отримайте ффт кожного компонента. Ви отримаєте впевненість у своєму програмуванні. Тоді ви можете судити про достовірність вашого результату. Якщо сигнал, який ви намагаєтеся проаналізувати, є першим, який ви коли-небудь брали з рук, тоді ви завжди будете відчувати, що робите щось не так ...
ssm

12

Високий стрибок, який у вас є, зумовлений постійною (не змінюється, тобто частотою = 0) частиною вашого сигналу. Це питання масштабу. Якщо ви хочете побачити вміст частоти, що не є постійним струмом, для візуалізації вам може знадобитися побудувати графік від зміщення 1, а не від зміщення 0 ШПФ сигналу.

Змінюючи приклад, наведений вище @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:])

Вихідні графіки: Побудова БПФ-сигналу постійним струмом, а потім при його видаленні (пропускання частоти = 0)

Інший спосіб - це візуалізація даних у масштабі журналу:

Використання:

plt.semilogy(xf, 2.0/N * np.abs(yf[0:N/2]))

Покаже: введіть тут опис зображення


Так, це в Гц. У коді, визначення, xfвідображає fft-бункери на частоти.
hesham_EE

1
Приємно! А як щодо осі y? Амплітуда? Велике спасибі hesham_EE
Віктор Агіар,

Так, вісь y - це абсолютне значення комплексу fft. Зверніть увагу на використанняnp.abs()
hesham_EE

8

Як доповнення до вже наведених відповідей, я хотів би зазначити, що часто важливо грати з розміром бункерів для ШПФ. Мало б сенс перевірити купу значень і вибрати те, яке має більше сенсу для вашої програми. Часто це в одній і тій же величині кількості зразків. Це було передбачено більшістю поданих відповідей і дає чудові та обґрунтовані результати. Якщо хтось хоче це вивчити, ось моя версія коду:

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

вихідні графіки: введіть тут опис зображення


5

Я створив функцію, яка займається побудовою ШПФ реальних сигналів. Додатковий бонус у моїй функції щодо наведених вище повідомлень полягає в тому, що ви отримуєте АКТУАЛЬНУ амплітуду сигналу. Крім того, через припущення реального сигналу, ШПФ є симетричним, тому ми можемо побудувати лише позитивну сторону осі х:

import matplotlib.pyplot as plt
import numpy as np
import warnings


def fftPlot(sig, dt=None, 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()

    return sigFFTPos, freqAxisPos


if __name__ == "__main__":
    dt = 1 / 1000

    # build a signal within nyquist - the result will be the positive FFT with actual magnitude
    f0 = 200  # [Hz]
    t = np.arange(0, 1 + dt, dt)
    sig = 1 * np.sin(2 * np.pi * f0 * t) + \
        10 * np.sin(2 * np.pi * f0 / 2 * t) + \
        3 * np.sin(2 * np.pi * f0 / 4 * t) +\
        7.5 * np.sin(2 * np.pi * f0 / 5 * t)

    # res in freqs
    fftPlot(sig, dt=dt)
    # res in samples (if freqs axis is unknown)
    fftPlot(sig)

аналітичний результат побудови ШПФ


4

На цій сторінці вже є чудові рішення, але всі припустили, що набір даних рівномірно / рівномірно вибрано / розподілено. Я спробую навести більш загальний приклад випадково відібраних даних. Я також буду використовувати цей підручник MATLAB як приклад:

Додавання необхідних модулів:

import numpy as np
import matplotlib.pyplot as plt
import scipy.fftpack
import scipy.signal

Генерування зразкових даних:

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

Сортування набору даних:

order = np.argsort(t)
ts = np.array(t)[order]
Xs = np.array(X)[order]

Передискретизація:

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)

побудова даних та передискретизація даних:

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

введіть тут опис зображення

тепер обчислюємо 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)

введіть тут опис зображення

PS Нарешті я встиг реалізувати більш канонічний алгоритм, щоб отримати перетворення Фур'є нерівномірно розподілених даних. Ви можете побачити код, опис і приклад Jupyter ноутбук тут .


У документах я не бачу нічого, що вказує на resampleобробку неоднорідного часу вибірки. Він дійсно приймає часовий параметр (який не використовується у прикладі), але, схоже, це також передбачає однаковий час вибірки.
user2699

@ user2699 цей приклад може допомогти
Foad

@ user2699 Відредагував код. Будемо вдячні, якщо ви зможете поглянути і повідомити мене, чи зараз це нормально.
Foad

1
"scipy.signal.resample" використовує метод ШПФ для повторної вибірки даних. Немає сенсу використовувати його для вибірки неоднорідних даних, щоб отримати єдиний ШПФ.
user2699

1
Усі методи, які ви надали, мають переваги та недоліки (хоча зауважте, що sklearn.utils.resampleінтерполяція не виконується). Якщо ви хочете обговорити доступні варіанти пошуку частот нерегулярно дискретизованого сигналу, або переваги різних типів інтерполяції, будь ласка, розпочніть інше питання. І те, і інше є цікавими предметами, але далеко за рамками відповідей про те, як скласти план ШПФ.
user2699

4

Я пишу цю додаткову відповідь, щоб пояснити походження дифузії шипів при використанні fft, і особливо обговорюю підручник із scipy.fftpack, з яким я в якийсь момент не погоджуюсь.

У цьому прикладі час запису tmax=N*T=0.75. Сигнал є sin(50*2*pi*x)+0.5*sin(80*2*pi*x). Частотний сигнал повинен містити 2 спайки на частотах 50і 80з амплітудами 1і 0.5. Однак, якщо аналізований сигнал не має цілого числа періодів, може з'явитися дифузія через скорочення сигналу:

  • Щука 1: 50*tmax=37.5=> частота 50не кратна 1/tmax=> Наявність дифузії внаслідок усічення сигналу на цій частоті.
  • Pike 2: 80*tmax=60=> частота 80кратна 1/tmax=> Відсутність дифузії через усічення сигналу на цій частоті.

Ось код, який аналізує той самий сигнал, що і в підручнику ( sin(50*2*pi*x)+0.5*sin(80*2*pi*x)), але з невеликими відмінностями:

  1. Оригінальний приклад scipy.fftpack.
  2. Оригінальний приклад scipy.fftpack із цілим числом періодів сигналу ( tmax=1.0замість того, 0.75щоб уникнути дифузії усічення).
  3. Оригінальний приклад scipy.fftpack із цілим числом періодів сигналу, де дати та частоти взяті з теорії ШПФ.

Кодекс:

import numpy as np
import matplotlib.pyplot as plt
import scipy.fftpack

# 1. Linspace
N = 600
# sample spacing
tmax = 3/4
T = tmax / N # =1.0 / 800.0
x1 = np.linspace(0.0, N*T, N)
y1 = np.sin(50.0 * 2.0*np.pi*x1) + 0.5*np.sin(80.0 * 2.0*np.pi*x1)
yf1 = scipy.fftpack.fft(y1)
xf1 = np.linspace(0.0, 1.0/(2.0*T), N//2)

# 2. Integer number of periods
tmax = 1
T = tmax / N # sample spacing
x2 = np.linspace(0.0, N*T, N)
y2 = np.sin(50.0 * 2.0*np.pi*x2) + 0.5*np.sin(80.0 * 2.0*np.pi*x2)
yf2 = scipy.fftpack.fft(y2)
xf2 = np.linspace(0.0, 1.0/(2.0*T), N//2)

# 3. Correct positionning of dates relatively to FFT theory (arange instead of linspace)
tmax = 1
T = tmax / N # sample spacing
x3 = T * np.arange(N)
y3 = np.sin(50.0 * 2.0*np.pi*x3) + 0.5*np.sin(80.0 * 2.0*np.pi*x3)
yf3 = scipy.fftpack.fft(y3)
xf3 = 1/(N*T) * np.arange(N)[:N//2]

fig, ax = plt.subplots()
# Plotting only the left part of the spectrum to not show aliasing
ax.plot(xf1, 2.0/N * np.abs(yf1[:N//2]), label='fftpack tutorial')
ax.plot(xf2, 2.0/N * np.abs(yf2[:N//2]), label='Integer number of periods')
ax.plot(xf3, 2.0/N * np.abs(yf3[:N//2]), label='Correct positionning of dates')
plt.legend()
plt.grid()
plt.show()

Вихід:

Як це може бути тут, навіть при використанні цілого числа періодів деяка дифузія все ще залишається. Така поведінка пов’язана з поганим розташуванням дат та частот у підручнику scipy.fftpack. Отже, в теорії дискретних перетворень Фур'є:

  • сигнал слід оцінювати за дати, t=0,T,...,(N-1)*Tколи T - період вибірки, а загальна тривалість сигналу - tmax=N*T. Зверніть увагу, що ми зупиняємось на tmax-T.
  • пов'язані частоти - це f=0,df,...,(N-1)*dfде df=1/tmax=1/(N*T)частота дискретизації. Усі гармоніки сигналу повинні бути кратними частоті дискретизації, щоб уникнути дифузії.

У наведеному вище прикладі ви можете бачити, що використання arangeзамість linspaceдозволяє уникнути додаткової дифузії в частотному спектрі. Більше того, використання linspaceверсії також призводить до зміщення спайків, які розташовані на дещо вищих частотах, ніж вони повинні бути, як це видно на першому малюнку, де спайки знаходяться трохи праворуч від частот 50і 80.

Я просто підсумую, що приклад використання слід замінити таким кодом (що, на мій погляд, менш оманливе):

import numpy as np
from scipy.fftpack import fft
# Number of sample points
N = 600
T = 1.0 / 800.0
x = T*np.arange(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 = 1/(N*T)*np.arange(N//2)
import matplotlib.pyplot as plt
plt.plot(xf, 2.0/N * np.abs(yf[0:N//2]))
plt.grid()
plt.show()

Результат (другий спайк більше не розсіюється):

Я думаю, що ця відповідь все ж приносить деякі додаткові пояснення щодо того, як правильно застосовувати дискретне перетворення Фур'є. Очевидно, що моя відповідь занадто довга, і завжди є що сказати (@ewerlopes коротко говорив про, наприклад, псевдонім, і багато чого можна сказати про вікна ), тому я зупинюсь . Я думаю, що дуже важливо глибоко зрозуміти принципи дискретного перетворення Фур'є, застосовуючи його, тому що всі ми знаємо стільки людей, які додають фактори тут і там, застосовуючи його, щоб отримати бажане.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.