Skip to content

Commit 1eb0f00

Browse files
committed
add F-test to mt_pgram
1 parent 08f4b0f commit 1eb0f00

File tree

3 files changed

+55
-6
lines changed

3 files changed

+55
-6
lines changed

Project.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ version = "0.8.3"
44

55
[deps]
66
Bessels = "0e736298-9ec6-45e8-9647-e4fc86a2fe38"
7+
Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f"
78
FFTW = "7a1cc6ca-52ef-59f5-83cd-3a7055c09341"
89
IterTools = "c8e1da08-722c-5040-9ed9-7db0dc04731e"
910
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
@@ -22,6 +23,7 @@ OffsetArraysExt = "OffsetArrays"
2223
[compat]
2324
Bessels = "0.2"
2425
DelimitedFiles = "1.6"
26+
Distributions = "0.25.119"
2527
FFTW = "1.8"
2628
IterTools = "1.4"
2729
LinearAlgebra = "1.6"

src/multitaper.jl

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
##### Multitapered periodogram
33
#####
44

5+
using Distributions
6+
57
struct MTConfig{T,R1,F,P,T1,T2,W,R2}
68
n_samples::Int
79
fs::R1
@@ -13,11 +15,12 @@ struct MTConfig{T,R1,F,P,T1,T2,W,R2}
1315
fft_output_tmp::T2
1416
window::W
1517
onesided::Bool
18+
ftest::Bool
1619
r::R2 # inverse normalization; e.g. equal to `fs*N` for an unwindowed/untapered periodogram of a signal of length `N`
1720
# e.g. equal to `fs*ntapers*ones(ntapers)` when tapered by properly normalized window functions (i.e. norm-2 equal to 1 for each taper)
1821
# can be adjusted to weight the tapers, e.g. by the eigenvalues of the DPSS windows
1922
function MTConfig{T}(n_samples, nfft, ntapers, freq::F, fs::R1, plan::P, fft_input_tmp::T1,
20-
fft_output_tmp::T2, window::W, onesided, r::R2) where {T,R1,F,P,T1,T2,W,R2}
23+
fft_output_tmp::T2, window::W, onesided, ftest, r::R2) where {T,R1,F,P,T1,T2,W,R2}
2124
n_samples > 0 || throw(ArgumentError("`n_samples` must be positive"))
2225
nfft >= n_samples || throw(ArgumentError("Must have `nfft >= n_samples`"))
2326
ntapers > 0 || throw(ArgumentError("`ntapers` must be positive"))
@@ -44,7 +47,7 @@ struct MTConfig{T,R1,F,P,T1,T2,W,R2}
4447
ntapers, freq, plan,
4548
fft_input_tmp,
4649
fft_output_tmp, window,
47-
onesided, r)
50+
onesided, ftest, r)
4851
end
4952
end
5053

@@ -85,6 +88,7 @@ end
8588
ntapers = 2 * nw - 1,
8689
taper_weights = fill(1/ntapers, ntapers),
8790
onesided::Bool=T<:Real,
91+
ftest::Bool=false,
8892
fft_flags = FFTW.MEASURE)
8993
9094
Creates a config object which holds the configuration state
@@ -107,11 +111,12 @@ as none of the input arguments change.
107111
The default setting is to simply average them.
108112
* `onesided`: whether or not to compute a "one-sided" FFT by using that real signal data
109113
yields conjugate-symmetry in Fourier space.
114+
* `ftest: whether or not to compute ...
110115
* `fft_flags`: flags to control how the FFT plan is generated.
111116
"""
112117
function MTConfig{T}(n_samples; fs=1, nfft=nextpow(2, n_samples), window=nothing, nw=4,
113118
ntapers=2 * nw - 1, taper_weights = fill(1/ntapers, ntapers),
114-
onesided::Bool=T <: Real,
119+
onesided::Bool=T <: Real, ftest::Bool = false,
115120
fft_flags=FFTW.MEASURE) where {T}
116121
if onesided && T <: Complex
117122
throw(ArgumentError("cannot compute one-sided FFT of a complex signal"))
@@ -131,7 +136,7 @@ function MTConfig{T}(n_samples; fs=1, nfft=nextpow(2, n_samples), window=nothing
131136
end
132137

133138
return MTConfig{T}(n_samples, nfft, ntapers, freq, fs, plan, fft_input_tmp,
134-
fft_output_tmp, window, onesided, r)
139+
fft_output_tmp, window, onesided, ftest, r)
135140
end
136141

137142
function allocate_output(config::MTConfig{T}) where {T}
@@ -222,6 +227,27 @@ See also [`mt_pgram`](@ref) and [`MTConfig`](@ref).
222227
"""
223228
mt_pgram!
224229

230+
function ftest(J::Array{ComplexF64,2}, tapers::Matrix)
231+
K = size(tapers,2)
232+
Kodd = 1:2:K
233+
Keven = 2:2:K
234+
235+
Jp = J[:,Kodd]
236+
H0 = sum(tapers[:,Kodd], dims=1)
237+
H0sq = sum(abs2, H0)
238+
JpH0 = dropdims(sum(Jp.*H0, dims=2), dims=2)
239+
A = JpH0 ./ H0sq
240+
Jhat = A * H0
241+
242+
num = (K-1) * abs2.(A) .* H0sq
243+
den = dropdims(sum(abs2.(Jp.-Jhat), dims=2), dims=2) +
244+
dropdims(sum(abs2.(J[:,Keven,:]), dims=2), dims=2)
245+
Fval = num ./ den
246+
247+
map!(x -> 1.0 - cdf(FDist(2, 2*Int64(K)-2), x), Fval, Fval)
248+
return Fval
249+
end
250+
225251
function mt_pgram!(output::AbstractVector, signal::AbstractVector, config::MTConfig)
226252
if length(output) != length(config.freq)
227253
throw(DimensionMismatch(lazy"""Expected `output` to be of length `length(config.freq)`;
@@ -233,12 +259,16 @@ function mt_pgram!(output::AbstractVector, signal::AbstractVector, config::MTCon
233259
end
234260
fft_output = config.fft_output_tmp
235261

262+
config.ftest && (ftest_scratch = similar(fft_output, (size(fft_output)..., config.ntapers)))
263+
236264
output .= 0
237265
for taper_index in 1:(config.ntapers)
238266
mt_fft_tapered!(fft_output, signal, taper_index, config)
267+
config.ftest && (ftest_scratch[:,taper_index] .= fft_output)
239268
fft2pow!(output, fft_output, config.nfft, config.r[taper_index], config.onesided)
240269
end
241-
return Periodogram(output, config.freq)
270+
Fval = ftest(ftest_scratch, config.window)
271+
return PeriodogramF(output, config.freq, Fval[:,1])
242272
end
243273

244274
#####

src/periodograms.jl

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ using ..Util, ..Windows
66
using Statistics: mean!
77
export arraysplit, nextfastfft, periodogram,
88
WelchConfig, welch_pgram, welch_pgram!,
9-
spectrogram, power, freq, stft,
9+
spectrogram, power, freq, Fpval, stft,
1010
MTConfig, mt_pgram, mt_pgram!,
1111
MTSpectrogramConfig, mt_spectrogram, mt_spectrogram!,
1212
MTCrossSpectraConfig, mt_cross_power_spectra, mt_cross_power_spectra!,
@@ -287,6 +287,23 @@ struct Periodogram2{T,F1<:Union{Frequencies,AbstractRange},F2<:Union{Frequencies
287287
freq2::F2
288288
end
289289

290+
"""
291+
PeriodogramF{T, F<:Union{Frequencies,AbstractRange}, F2<:Union{Frequencies,AbstractRange}, M<:AbstractMatrix{T}} <: TFR{T}
292+
293+
A PeriodogramF object with fields:
294+
- `power::M`
295+
- `freq1::F`
296+
- `Ftest::P`
297+
See [`power`](@ref) and [`freq`](@ref) for further details.
298+
"""
299+
struct PeriodogramF{T,F<:Union{Frequencies,AbstractRange},P<:Vector{Float64},V<:AbstractVector{T}} <: TFR{T}
300+
power::V
301+
freq::F
302+
Fpval::P
303+
end
304+
305+
Fpval(p::PeriodogramF) = p.Fpval
306+
290307
"""
291308
power(p)
292309

0 commit comments

Comments
 (0)