-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add seasonality detection using periodograms, and Python/JS bindings (#…
…61) * Add seasonality detection using periodograms, and Python/JS bindings * Use u32 instead of usize; return Vec instead of impl Iterator for lower MSRV * Change Detector trait to pass data to detect This means detectors don't need to store the data and can avoid a lifetime parameter. * Allow seasonality detector params to be customised in JS bindings * Allow seasonality detector params to be customised in Python bindings
- Loading branch information
Showing
16 changed files
with
1,956 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
//! Javascript bindings for augurs seasonality detection. | ||
use serde::Deserialize; | ||
use wasm_bindgen::prelude::*; | ||
|
||
use augurs_seasons::{Detector, PeriodogramDetector}; | ||
|
||
/// Options for detecting seasonal periods. | ||
#[derive(Debug, Default, Deserialize)] | ||
pub struct SeasonalityOptions { | ||
/// The minimum period to consider when detecting seasonal periods. | ||
/// | ||
/// The default is 4. | ||
pub min_period: Option<u32>, | ||
|
||
/// The maximum period to consider when detecting seasonal periods. | ||
/// | ||
/// The default is the length of the data divided by 3, or 512, whichever is smaller. | ||
pub max_period: Option<u32>, | ||
|
||
/// The threshold for detecting peaks in the periodogram. | ||
/// | ||
/// The value will be clamped to the range 0.01 to 0.99. | ||
/// | ||
/// The default is 0.9. | ||
pub threshold: Option<f64>, | ||
} | ||
|
||
impl From<SeasonalityOptions> for PeriodogramDetector { | ||
fn from(options: SeasonalityOptions) -> Self { | ||
let mut builder = PeriodogramDetector::builder(); | ||
if let Some(min_period) = options.min_period { | ||
builder = builder.min_period(min_period); | ||
} | ||
if let Some(max_period) = options.max_period { | ||
builder = builder.max_period(max_period); | ||
} | ||
if let Some(threshold) = options.threshold { | ||
builder = builder.threshold(threshold); | ||
} | ||
builder.build() | ||
} | ||
} | ||
|
||
/// Detect the seasonal periods in a time series. | ||
#[wasm_bindgen] | ||
pub fn seasonalities(y: &[f64], options: JsValue) -> Vec<u32> { | ||
let options: SeasonalityOptions = | ||
serde_wasm_bindgen::from_value::<Option<SeasonalityOptions>>(options) | ||
.ok() | ||
.flatten() | ||
.unwrap_or_default(); | ||
PeriodogramDetector::from(options).detect(y) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
[package] | ||
name = "augurs-seasons" | ||
version.workspace = true | ||
authors.workspace = true | ||
documentation.workspace = true | ||
repository.workspace = true | ||
license.workspace = true | ||
edition.workspace = true | ||
keywords.workspace = true | ||
description = "Seasonality detection using periodograms" | ||
|
||
[dependencies] | ||
itertools.workspace = true | ||
num-traits = "0.2.18" | ||
thiserror.workspace = true | ||
tracing.workspace = true | ||
welch-sde = "0.1.0" | ||
|
||
[dev-dependencies] | ||
augurs-testing.workspace = true | ||
criterion.workspace = true | ||
pprof.workspace = true | ||
|
||
[[bench]] | ||
name = "periodogram" | ||
harness = false |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
# Seasonality detection for time series | ||
|
||
`augurs-seasons` contains methods for detecting seasonality or periodicity in time series. | ||
|
||
It currently contains implementations to do so using periodograms, similar to the [`seasonal`] Python package. | ||
|
||
## Usage | ||
|
||
```rust | ||
use augurs_seasons::{Detector, PeriodogramDetector}; | ||
|
||
# fn main() { | ||
let y = &[ | ||
0.1, 0.3, 0.8, 0.5, | ||
0.1, 0.31, 0.79, 0.48, | ||
0.09, 0.29, 0.81, 0.49, | ||
0.11, 0.28, 0.78, 0.53, | ||
0.1, 0.3, 0.8, 0.5, | ||
0.1, 0.31, 0.79, 0.48, | ||
0.09, 0.29, 0.81, 0.49, | ||
0.11, 0.28, 0.78, 0.53, | ||
]; | ||
// Use the detector with default parameters. | ||
let periods = PeriodogramDetector::default().detect(y); | ||
assert_eq!(periods[0], 4); | ||
|
||
// Customise the detector using the builder. | ||
let periods = PeriodogramDetector::builder() | ||
.min_period(4) | ||
.max_period(8) | ||
.threshold(0.8) | ||
.build() | ||
.detect(y); | ||
assert_eq!(periods[0], 4); | ||
# } | ||
``` | ||
|
||
## Credits | ||
|
||
This implementation is based heavily on the [`seasonal`] Python package. | ||
It also makes heavy use of the [`welch-sde`] crate. | ||
|
||
[`seasonal`]: https://github.com/welch/seasonal | ||
[`welch-sde`]: https://crates.io/crates/welch-sde | ||
|
||
## License | ||
|
||
Dual-licensed to be compatible with the Rust project. | ||
Licensed under the Apache License, Version 2.0 `<http://www.apache.org/licenses/LICENSE-2.0>` or the MIT license `<http://opensource.org/licenses/MIT>`, at your option. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
use criterion::{criterion_group, criterion_main, Criterion}; | ||
use pprof::criterion::{Output, PProfProfiler}; | ||
|
||
use augurs_seasons::{Detector, PeriodogramDetector}; | ||
use augurs_testing::data::SEASON_EIGHT; | ||
|
||
fn season_eight(c: &mut Criterion) { | ||
let y = SEASON_EIGHT; | ||
let detector = PeriodogramDetector::builder().build(); | ||
c.bench_function("season_eight", |b| { | ||
b.iter(|| detector.detect(y)); | ||
}); | ||
} | ||
|
||
criterion_group! { | ||
name = benches; | ||
config = Criterion::default().with_profiler(PProfProfiler::new(100, Output::Protobuf)); | ||
targets = season_eight | ||
} | ||
criterion_main!(benches); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
#![doc = include_str!("../README.md")] | ||
#![warn( | ||
missing_docs, | ||
missing_debug_implementations, | ||
rust_2018_idioms, | ||
unreachable_pub | ||
)] | ||
|
||
mod periodogram; | ||
#[cfg(test)] | ||
mod test_data; | ||
|
||
pub use periodogram::{ | ||
Builder as PeriodogramDetectorBuilder, Detector as PeriodogramDetector, Periodogram, | ||
}; | ||
|
||
/// A detector of periodic signals in a time series. | ||
pub trait Detector { | ||
/// Detects the periods of a time series. | ||
fn detect(&self, data: &[f64]) -> Vec<u32>; | ||
} |
Oops, something went wrong.