Engee 文档
Notebook

趋势和谐波分量(傅立叶分解)

时间序列分析是研究各种过程的重要工具,无论是环境测量,设备的技术特征还是生物有机体的生命体征。

时间序列处理的关键阶段之一是识别隐藏在测量数据中的模式,趋势和周期性成分。 这些组件使人们能够更好地理解所研究现象的动态,并做出明智的预测。

这项工作致力于通过光谱分析研究汽车排气中所含一氧化碳(CO)浓度的时间依赖性。 为了解决这个问题,已经开发了一种特殊的算法,其中包括几个顺序步骤。 让我们列出他们。

  1. 数据解析:从文件中读取原始数据并将其转换为方便的格式以供进一步处理。

  2. 添加时间线:计算每个观测值相对于测量开始的第二个标记。

  3. 趋势识别:通过在滑动窗口中取平均值,然后用三度多项式近似来识别CO水平的长期趋势。

  4. 频谱分析:应用快速傅立叶变换(FFT)来识别显着的振荡频率。

  5. 信号重建:在去除趋势后恢复形成过程结构的主要谐波分量。

  6. 最终可视化:重建信号与初始数据的比较并评估所提出方法的质量。

在本文中,我们将以真实实验数据为例,展示一种分析方法识别时间序列特征的可能性。

算法实现说明

算法的主要阶段

1. 数据准备

该函数用于准备数据 parse_line,它允许将字符串拆分为单独的数值并将其转换为适当的数据类型。 解析后,数据被加载到一个表中,如 DataFrame.

然后以秒格式添加时间戳,相对于第一次测量计算。

2. 趋势识别

有两种方法可以确定趋势:
-固定大小的滑动窗口中的平均值,
-通过第三度多项式近似。

第一种方法简单方便,允许您直观地评估整体趋势,而第二种方法允许精确的参数模型。

3. 光谱分析

趋势消除后,应用快速傅立叶变换(FFT)。 因此,可以识别该时间序列的主导频率特性。 在分析获得的光谱期间,区分三个最大分量(谐波)。

4. 信号重建

基于所选择的谐波,重建近似信号,其叠加在原始曲线上,并且趋势用于评估恢复的准确性。

In [ ]:
# Функция для парсинга одной строки
function parse_line(line)
    # Заменяем запятые на точки для корректного парсинга чисел
    line = replace(line, "," => ".")
    # Разделяем по пробелам/табам и фильтруем пустые элементы
    elements = filter(!isempty, split(line, r"\s+"))
    # Преобразуем элементы в нужные типы
    co = parse(Float64, elements[1])
    ch = parse(Float64, elements[2])
    co2 = parse(Float64, elements[3])
    o2 = parse(Float64, elements[4])
    lamb = parse(Float64, elements[5])
    n = parse(Int, elements[6])
    nox = parse(Int, elements[7])
    tmas = parse(Float64, elements[8])
    time = Time(elements[9])
    return (CO=co, CH=ch, CO2=co2, O2=o2, lamb=lamb, n=n, NOx=nox, Tmas=tmas, Time=time)
end
Out[0]:
parse_line (generic function with 1 method)
In [ ]:
using DataFrames
# Чтение данных
data_lines = readlines("$(@__DIR__)/data.txt")[2:end]  # Пропускаем заголовок
parsed_data = [parse_line(line) for line in data_lines] # Парсим все строки
df = DataFrame(parsed_data) # Преобразуем в DataFrame
println(first(df, 5)) # Выводим первые 5 строк для проверки
println()
using Dates, FFTW, Statistics
# Добавим колонку с временем в секундах
start_time = df.Time[1]
df[!, :Seconds] = [Dates.value(t - start_time) / 1e9 for t in df.Time]  # переводим наносекунды в секунды
println(first(df, 5)) # Выводим первые 5 строк для проверки
5×9 DataFrame
 Row │ CO       CH       CO2      O2       lamb     n      NOx    Tmas     Time
     │ Float64  Float64  Float64  Float64  Float64  Int64  Int64  Float64  Time
─────┼──────────────────────────────────────────────────────────────────────────────
   1 │    0.64    113.0    13.83     0.75    1.012    820     13      0.0  00:00:00
   2 │    0.64    112.0    13.83     0.75    1.012    770    185      0.0  15:56:25
   3 │    0.64    112.0    13.83     0.75    1.012    790      0      0.0  15:56:26
   4 │    0.64    112.0    13.83     0.75    1.012    800      0      0.0  15:56:27
   5 │    0.64    112.0    13.84     0.74    1.012    990      0      0.0  15:56:28

5×10 DataFrame
 Row │ CO       CH       CO2      O2       lamb     n      NOx    Tmas     Time      Seconds
     │ Float64  Float64  Float64  Float64  Float64  Int64  Int64  Float64  Time      Float64
─────┼───────────────────────────────────────────────────────────────────────────────────────
   1 │    0.64    113.0    13.83     0.75    1.012    820     13      0.0  00:00:00      0.0
   2 │    0.64    112.0    13.83     0.75    1.012    770    185      0.0  15:56:25  57385.0
   3 │    0.64    112.0    13.83     0.75    1.012    790      0      0.0  15:56:26  57386.0
   4 │    0.64    112.0    13.83     0.75    1.012    800      0      0.0  15:56:27  57387.0
   5 │    0.64    112.0    13.84     0.74    1.012    990      0      0.0  15:56:28  57388.0

接下来,我们继续选择一个变量进行分析。 让我们取变量CO(你可以用任何其他一个替换它):

In [ ]:
i = 1 # Номер столбца для анализа
y = df[:,i]
column_names = names(df) # Получаем имена столбцов
println("Имя столбца для анализа: $(column_names[i])")
t = df.Seconds.-(15*60*60+56*60); t[1] = 0;
println("t: $(t[1:5])") # Выводим первые 5 строк для проверки
Имя столбца для анализа: CO
t: [0.0, 25.0, 26.0, 27.0, 28.0]

接下来,我们将构建趋势组件。 我们使用移动平均线来突出趋势。:

In [ ]:
window_size = 15  # размер окна для сглаживания
trend = [mean(y[max(1, i-window_size):min(end, i+window_size)]) for i in 1:length(y)]

# График исходных данных и тренда
plot(t, y, label="Исходные данные", xlabel="Время (с)", ylabel="CO", legend=:topleft)
plot!(t, trend, label="Тренд", linewidth=2)
Out[0]:

为了获得更准确的趋势,您可以使用多项式近似而不是移动平均线。:

In [ ]:
using Polynomials
trend_coef = fit(t, y, 3)  # полином третьей степени
trend = trend_coef.(t)

# График исходных данных и тренда
plot(t, y, label="Исходные данные", xlabel="Время (с)", ylabel="CO", legend=:topleft)
plot!(t, trend, label="Тренд", linewidth=2)
Out[0]:

让我们对谐波分量进行傅立叶分解。

In [ ]:
# Удаляем тренд для анализа периодических составляющих
detrended = y .- trend

# Вычисляем БПФ
n = length(detrended)
freq = fftfreq(n, 1/mean(diff(t)))  # частоты в Гц
fft_vals = fft(detrended)

# Амплитуды и фазы
amplitude = abs.(fft_vals) ./ n * 2
phase = angle.(fft_vals)

# Оставляем только положительные частоты (симметрия БПФ)
pos_freq = freq[1:div(n,2)+1]
pos_ampl = amplitude[1:div(n,2)+1]

# График амплитудного спектра
plot(pos_freq, pos_ampl, xlabel="Частота (Гц)", ylabel="Амплитуда", 
     title="Амплитудный спектр", legend=false)
Out[0]:

让我们恢复主要谐波。 让我们选择一些最重要的频率并恢复它们的贡献。:

In [ ]:
# Находим 3 наиболее значимые частоты
top_n = 3
sorted_idx = sortperm(pos_ampl, rev=true)
top_freq = pos_freq[sorted_idx[1:top_n]]
top_ampl = pos_ampl[sorted_idx[1:top_n]]
top_phase = phase[sorted_idx[1:top_n]]

# Восстанавливаем гармоники
harmonics = zeros(n)
for (f, a, ϕ) in zip(top_freq, top_ampl, top_phase)
    harmonics .+= a .* cos.(2π * f * t .+ ϕ)
end

# Итоговый график
plot(t, detrended, label="Детрендированные данные", xlabel="Время (с)", ylabel="CO")
plot!(t, harmonics, label="Основные гармоники", linewidth=2)
Out[0]:

完全分解:趋势和谐波。

In [ ]:
plot(t, y, label="Исходные данные", xlabel="Время (с)", ylabel="CO")
plot!(t, trend, label="Тренд", linewidth=2)
plot!(t, trend .+ harmonics, label="Тренд + гармоники", linewidth=2, linestyle=:dash)
Out[0]:

结论

分析揭示了所研究时间序列行为的以下重要方面。

-CO水平的长期波动突出显示,由车辆运动和外部环境因素确定的明显趋势表示。
-确定了CO浓度周期性变化的结构,通过三个主要振荡频率的分配来表达。
-对原始信号的重建质量进行了评估,其中显示了减损数据与基波谐波之间的良好相关性。

因此,开发的方法被证明对分析信号(如物质浓度的时间依赖性)是有效的,允许研究人员甚至从真实实验中的嘈杂数据中提取有用的信息。

所提出的算法可以很容易地适应于分析其他研究领域的类似时间序列,其中将数据分离为趋势和高频波动是很重要的。