use std::fmt;
#[derive(Debug, Clone)]
pub struct TimeSeries<T> {
pub timestamps: Vec<T>,
pub values: Vec<Option<T>>,
}
impl<T> TimeSeries<T> {
pub fn from_raw(values: Vec<T>) -> Result<Self, TimeSeriesError>
where
T: Copy + From<f64>,
{
if values.is_empty() {
return Err(TimeSeriesError::EmptyData);
}
let n = values.len();
let timestamps: Vec<T> = (0..n).map(|i| T::from(i as f64)).collect();
let values: Vec<Option<T>> = values.into_iter().map(Some).collect();
Ok(TimeSeries { timestamps, values })
}
pub fn new(timestamps: Vec<T>, values: Vec<Option<T>>) -> Result<Self, TimeSeriesError> {
if timestamps.len() != values.len() {
return Err(TimeSeriesError::LengthMismatch {
timestamps: timestamps.len(),
values: values.len(),
});
}
if timestamps.is_empty() {
return Err(TimeSeriesError::EmptyData);
}
Ok(TimeSeries { timestamps, values })
}
pub fn len(&self) -> usize {
self.values.len()
}
pub fn is_empty(&self) -> bool {
self.values.is_empty()
}
}
impl<T> TimeSeries<T> {
pub fn handle_missing(&self, strategy: crate::core::features::missing_data::MissingDataStrategy)
-> Result<Self, crate::core::features::missing_data::ImputationError>
where
T: Copy + PartialOrd + std::ops::Add<Output = T> + std::ops::Div<Output = T> + From<f64>,
{
use crate::core::features::missing_data::{MissingDataHandler, ImputationError};
let mut new_values = Vec::with_capacity(self.values.len());
for i in 0..self.values.len() {
let value = if let Some(v) = self.values[i] {
Some(v)
} else {
Some(strategy.handle(&self.values, i)
.ok_or(ImputationError::AllStrategiesFailed { index: i })?)
};
new_values.push(value);
}
Ok(TimeSeries {
timestamps: self.timestamps.clone(),
values: new_values,
})
}
}
#[derive(Debug, Clone)]
pub struct WindowedTimeSeries<T> {
pub windows: Vec<Vec<Vec<T>>>,
pub labels: Option<Vec<T>>,
pub series_indices: Vec<usize>,
pub window_starts: Vec<usize>,
pub window_size: usize,
pub num_features: usize,
}
impl<T> WindowedTimeSeries<T> {
pub fn from_series(series: &TimeSeries<T>, window_size: usize, stride: usize)
-> Result<Self, TimeSeriesError>
where
T: Copy + Default,
{
if series.len() < window_size {
return Err(TimeSeriesError::EmptyData);
}
let num_windows = ((series.len() - window_size) / stride) + 1;
let mut windows = Vec::with_capacity(num_windows);
let mut series_indices = Vec::with_capacity(num_windows);
let mut window_starts = Vec::with_capacity(num_windows);
for i in 0..num_windows {
let start_idx = i * stride;
series_indices.push(0); window_starts.push(start_idx);
let mut window = vec![vec![]; 1]; window[0] = (start_idx..start_idx + window_size)
.filter_map(|idx| series.values[idx])
.collect();
while window[0].len() < window_size {
window[0].push(T::default());
}
windows.push(window);
}
Ok(WindowedTimeSeries {
windows,
labels: None,
series_indices,
window_starts,
window_size,
num_features: 1,
})
}
pub fn from_multiple_series(series_vec: &[TimeSeries<T>], window_size: usize, stride: usize)
-> Result<Self, TimeSeriesError>
where
T: Copy + Default,
{
let mut all_windows = Vec::new();
let mut all_series_indices = Vec::new();
let mut all_window_starts = Vec::new();
for (series_idx, series) in series_vec.iter().enumerate() {
if series.len() < window_size {
continue; }
let num_windows = ((series.len() - window_size) / stride) + 1;
for i in 0..num_windows {
let start_idx = i * stride;
all_series_indices.push(series_idx);
all_window_starts.push(start_idx);
let mut window = vec![vec![]; 1]; window[0] = (start_idx..start_idx + window_size)
.filter_map(|idx| series.values[idx])
.collect();
while window[0].len() < window_size {
window[0].push(T::default());
}
all_windows.push(window);
}
}
Ok(WindowedTimeSeries {
windows: all_windows,
labels: None,
series_indices: all_series_indices,
window_starts: all_window_starts,
window_size,
num_features: 1,
})
}
pub fn len(&self) -> usize {
self.windows.len()
}
pub fn is_empty(&self) -> bool {
self.windows.is_empty()
}
pub fn get_window(&self, index: usize) -> Option<&Vec<Vec<T>>> {
self.windows.get(index)
}
pub fn with_labels(mut self, labels: Vec<T>) -> Self {
self.labels = Some(labels);
self
}
pub fn to_array(&self) -> Vec<Vec<Vec<T>>>
where
T: Clone,
{
self.windows.clone()
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum TimeSeriesError {
LengthMismatch {
timestamps: usize,
values: usize,
},
EmptyData,
NonMonotonicTimestamps,
}
impl fmt::Display for TimeSeriesError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
TimeSeriesError::LengthMismatch { timestamps, values } => {
write!(f, "Length mismatch: {} timestamps but {} values", timestamps, values)
}
TimeSeriesError::EmptyData => write!(f, "Time series is empty"),
TimeSeriesError::NonMonotonicTimestamps => {
write!(f, "Timestamps must be monotonically increasing")
}
}
}
}
impl std::error::Error for TimeSeriesError {}