Skip to content

Commit

Permalink
Support gaps in average calculation
Browse files Browse the repository at this point in the history
  • Loading branch information
senier committed Dec 1, 2024
1 parent 3da0829 commit 4572cdb
Showing 1 changed file with 169 additions and 39 deletions.
208 changes: 169 additions & 39 deletions frontend/src/ui/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1136,7 +1136,7 @@ pub fn centered_moving_grouping(
radius: u64,
group_day: impl Fn(Vec<f32>) -> Option<f32>,
group_range: impl Fn(Vec<f32>) -> Option<f32>,
) -> Vec<(NaiveDate, f32)> {
) -> Vec<Vec<(NaiveDate, f32)>> {
let mut date_map: BTreeMap<&NaiveDate, Vec<f32>> = BTreeMap::new();

for (date, value) in data {
Expand All @@ -1155,22 +1155,40 @@ pub fn centered_moving_grouping(
.first
.iter_days()
.take_while(|d| *d <= interval.last)
.filter_map(|center| {
group_range(
center
.checked_sub_days(Days::new(radius))
.unwrap_or(center)
.iter_days()
.take_while(|d| {
*d <= interval.last
&& *d <= center.checked_add_days(Days::new(radius)).unwrap_or(center)
})
.filter_map(|d| grouped.get(&d))
.copied()
.collect::<Vec<_>>(),
)
.map(|result| (center, result))
})
.fold(
vec![vec![]],
|mut result: Vec<Vec<(NaiveDate, f32)>>, center| {
let value = group_range(
center
.checked_sub_days(Days::new(radius))
.unwrap_or(center)
.iter_days()
.take_while(|d| {
*d <= interval.last
&& *d
<= center.checked_add_days(Days::new(radius)).unwrap_or(center)
})
.filter_map(|d| grouped.get(&d))
.copied()
.collect::<Vec<_>>(),
);
if let Some(last) = result.last_mut() {
match value {
Some(v) => {
last.push((center, v));
}
None => {
if !last.is_empty() {
result.push(vec![]);
}
}
}
}
result
},
)
.into_iter()
.filter(|v| !v.is_empty())
.collect::<Vec<_>>()
}

Expand All @@ -1193,7 +1211,8 @@ pub fn centered_moving_total(
radius,
|d| Some(d.iter().sum()),
|d| Some(d.iter().sum()),
)
)[0]
.clone()
}

/// Calculate a series of moving averages from a given series of (date, value) pairs.
Expand All @@ -1210,7 +1229,7 @@ pub fn centered_moving_average(
data: &Vec<(NaiveDate, f32)>,
interval: &Interval,
radius: u64,
) -> Vec<(NaiveDate, f32)> {
) -> Vec<Vec<(NaiveDate, f32)>> {
#[allow(clippy::cast_precision_loss)]
centered_moving_grouping(
data,
Expand Down Expand Up @@ -1373,21 +1392,75 @@ mod tests {
}

#[rstest]
#[case((2020, 2, 3), (2020, 2, 5), 0, &[], &[])]
#[case((2020, 2, 3), (2020, 2, 5), 0, &[(2020, 2, 3, 1.0)], &[(2020, 2, 3, 1.0)])]
#[case((2020, 3, 3), (2020, 3, 5), 0, &[(2020, 2, 3, 1.0)], &[])]
#[case((2020, 2, 3), (2020, 2, 5), 0, &[(2020, 2, 3, 1.0), (2020, 2, 4, 1.0), (2020, 2, 5, 1.0)], &[(2020, 2, 3, 1.0), (2020, 2, 4, 1.0), (2020, 2, 5, 1.0)])]
#[case((2020, 2, 3), (2020, 2, 5), 0, &[(2020, 2, 3, 1.0), (2020, 2, 4, 1.0), (2020, 2, 5, 1.0), (2020, 2, 3, 3.0)], &[(2020, 2, 3, 2.0), (2020, 2, 4, 1.0), (2020, 2, 5, 1.0)])]
#[case((2020, 2, 3), (2020, 2, 5), 1, &[(2020, 2, 3, 1.0), (2020, 2, 4, 2.0), (2020, 2, 5, 3.0)], &[(2020, 2, 3, 1.5), (2020, 2, 4, 2.0), (2020, 2, 5, 2.5)])]
#[case((2020, 2, 2), (2020, 2, 6), 1, &[(2020, 2, 3, 1.0), (2020, 2, 4, 2.0), (2020, 2, 5, 3.0)], &[(2020, 2, 2, 1.0), (2020, 2, 3, 1.5), (2020, 2, 4, 2.0), (2020, 2, 5, 2.5), (2020, 2, 6, 3.0)])]
#[case((2020, 2, 3), (2020, 2, 7), 1, &[(2020, 2, 3, 1.0), (2020, 2, 7, 1.0)], &[(2020, 2, 3, 1.0), (2020, 2, 4, 1.0), (2020, 2, 6, 1.0), (2020, 2, 7, 1.0)])]
#[case((2020, 2, 3), (2020, 2, 9), 1, &[(2020, 2, 3, 1.0), (2020, 2, 9, 1.0)], &[(2020, 2, 3, 1.0), (2020, 2, 4, 1.0), (2020, 2, 8, 1.0), (2020, 2, 9, 1.0)])]
#[case::empty_series(
(2020, 2, 3),
(2020, 2, 5),
0,
&[],
vec![]
)]
#[case::value_outside_interval(
(2020, 3, 3),
(2020, 3, 5),
0,
&[(2020, 2, 3, 1.0)],
vec![]
)]
#[case::zero_radius_single_value(
(2020, 2, 3),
(2020, 2, 5),
0,
&[(2020, 2, 3, 1.0)],
vec![vec![(2020, 2, 3, 1.0)]]
)]
#[case::zero_radius_multiple_days(
(2020, 2, 3),
(2020, 2, 5),
0,
&[(2020, 2, 3, 1.0), (2020, 2, 4, 1.0), (2020, 2, 5, 1.0)],
vec![vec![(2020, 2, 3, 1.0), (2020, 2, 4, 1.0), (2020, 2, 5, 1.0)]]
)]
#[case::zero_radius_multiple_values_per_day(
(2020, 2, 3),
(2020, 2, 5),
0,
&[(2020, 2, 3, 1.0), (2020, 2, 4, 1.0), (2020, 2, 5, 1.0), (2020, 2, 3, 3.0)],
vec![vec![(2020, 2, 3, 2.0), (2020, 2, 4, 1.0), (2020, 2, 5, 1.0)]]
)]
#[case::nonzero_radius_multiple_days(
(2020, 2, 3),
(2020, 2, 5),
1,
&[(2020, 2, 3, 1.0), (2020, 2, 4, 2.0), (2020, 2, 5, 3.0)],
vec![vec![(2020, 2, 3, 1.5), (2020, 2, 4, 2.0), (2020, 2, 5, 2.5)]]
)]
#[case::nonzero_radius_missing_day(
(2020, 2, 2),
(2020, 2, 6),
1,
&[(2020, 2, 3, 1.0), (2020, 2, 4, 2.0), (2020, 2, 5, 3.0)],
vec![vec![(2020, 2, 2, 1.0), (2020, 2, 3, 1.5), (2020, 2, 4, 2.0), (2020, 2, 5, 2.5), (2020, 2, 6, 3.0)]]
)]
#[case::nonzero_radius_with_gap_1(
(2020, 2, 3),
(2020, 2, 7),
1,
&[(2020, 2, 3, 1.0), (2020, 2, 7, 1.0)],
vec![vec![(2020, 2, 3, 1.0), (2020, 2, 4, 1.0)], vec![(2020, 2, 6, 1.0), (2020, 2, 7, 1.0)]]
)]
#[case::nonzero_radius_with_gap_2(
(2020, 2, 3),
(2020, 2, 9),
1,
&[(2020, 2, 3, 1.0), (2020, 2, 9, 1.0)],
vec![vec![(2020, 2, 3, 1.0), (2020, 2, 4, 1.0)], vec![(2020, 2, 8, 1.0), (2020, 2, 9, 1.0)]]
)]
fn centered_moving_average(
#[case] start: (i32, u32, u32),
#[case] end: (i32, u32, u32),
#[case] radius: u64,
#[case] input: &[(i32, u32, u32, f32)],
#[case] expected: &[(i32, u32, u32, f32)],
#[case] expected: Vec<Vec<(i32, u32, u32, f32)>>,
) {
assert_eq!(
super::centered_moving_average(
Expand All @@ -1403,21 +1476,78 @@ mod tests {
),
expected
.iter()
.map(|(y, m, d, v)| (NaiveDate::from_ymd_opt(*y, *m, *d).unwrap(), *v))
.map(|v| v
.iter()
.map(|(y, m, d, v)| (NaiveDate::from_ymd_opt(*y, *m, *d).unwrap(), *v))
.collect::<Vec<_>>())
.collect::<Vec<_>>(),
);
}

#[rstest]
#[case((2020, 2, 3), (2020, 2, 5), 0, &[], &[(2020, 2, 3, 0.0), (2020, 2, 4, 0.0), (2020, 2, 5, 0.0)])]
#[case((2020, 3, 3), (2020, 3, 5), 0, &[(2020, 2, 3, 1.0)], &[(2020, 3, 3, 0.0), (2020, 3, 4, 0.0), (2020, 3, 5, 0.0)])]
#[case((2020, 2, 3), (2020, 2, 5), 0, &[(2020, 2, 3, 1.0)], &[(2020, 2, 3, 1.0), (2020, 2, 4, 0.0), (2020, 2, 5, 0.0)])]
#[case((2020, 2, 3), (2020, 2, 5), 0, &[(2020, 2, 3, 1.0), (2020, 2, 4, 2.0), (2020, 2, 5, 3.0)], &[(2020, 2, 3, 1.0), (2020, 2, 4, 2.0), (2020, 2, 5, 3.0)])]
#[case((2020, 2, 3), (2020, 2, 5), 0, &[(2020, 2, 3, 1.0), (2020, 2, 4, 2.0), (2020, 2, 5, 3.0), (2020, 2, 3, 1.0)], &[(2020, 2, 3, 2.0), (2020, 2, 4, 2.0), (2020, 2, 5, 3.0)])]
#[case((2020, 2, 3), (2020, 2, 5), 1, &[(2020, 2, 3, 1.0), (2020, 2, 4, 2.0), (2020, 2, 5, 3.0)], &[(2020, 2, 3, 3.0), (2020, 2, 4, 6.0), (2020, 2, 5, 5.0)])]
#[case((2020, 2, 2), (2020, 2, 6), 1, &[(2020, 2, 3, 1.0), (2020, 2, 4, 2.0), (2020, 2, 5, 3.0)], &[(2020, 2, 2, 1.0), (2020, 2, 3, 3.0), (2020, 2, 4, 6.0), (2020, 2, 5, 5.0), (2020, 2, 6, 3.0)])]
#[case((2020, 2, 3), (2020, 2, 7), 1, &[(2020, 2, 3, 1.0), (2020, 2, 7, 1.0)], &[(2020, 2, 3, 1.0), (2020, 2, 4, 1.0), (2020, 2, 5, 0.0), (2020, 2, 6, 1.0), (2020, 2, 7, 1.0)])]
#[case((2020, 2, 3), (2020, 2, 9), 1, &[(2020, 2, 3, 1.0), (2020, 2, 9, 1.0)], &[(2020, 2, 3, 1.0), (2020, 2, 4, 1.0), (2020, 2, 5, 0.0), (2020, 2, 6, 0.0), (2020, 2, 7, 0.0), (2020, 2, 8, 1.0), (2020, 2, 9, 1.0)])]
#[case::empty_series(
(2020, 2, 3),
(2020, 2, 5),
0,
&[],
&[(2020, 2, 3, 0.0), (2020, 2, 4, 0.0), (2020, 2, 5, 0.0)],
)]
#[case::value_outside_interval(
(2020, 3, 3),
(2020, 3, 5),
0,
&[(2020, 2, 3, 1.0)],
&[(2020, 3, 3, 0.0), (2020, 3, 4, 0.0), (2020, 3, 5, 0.0)],
)]
#[case::zero_radius_single_day(
(2020, 2, 3),
(2020, 2, 5),
0,
&[(2020, 2, 3, 1.0)],
&[(2020, 2, 3, 1.0), (2020, 2, 4, 0.0), (2020, 2, 5, 0.0)],
)]
#[case::zero_radius_multiple_days(
(2020, 2, 3),
(2020, 2, 5),
0,
&[(2020, 2, 3, 1.0), (2020, 2, 4, 2.0), (2020, 2, 5, 3.0)],
&[(2020, 2, 3, 1.0), (2020, 2, 4, 2.0), (2020, 2, 5, 3.0)],
)]
#[case::zero_radius_multiple_values_per_day(
(2020, 2, 3),
(2020, 2, 5),
0,
&[(2020, 2, 3, 1.0), (2020, 2, 4, 2.0), (2020, 2, 5, 3.0), (2020, 2, 3, 1.0)],
&[(2020, 2, 3, 2.0), (2020, 2, 4, 2.0), (2020, 2, 5, 3.0)],
)]
#[case::nonzero_radius_multiple_days(
(2020, 2, 3),
(2020, 2, 5),
1,
&[(2020, 2, 3, 1.0), (2020, 2, 4, 2.0), (2020, 2, 5, 3.0)],
&[(2020, 2, 3, 3.0), (2020, 2, 4, 6.0), (2020, 2, 5, 5.0)],
)]
#[case::nonzero_radius_missing_day(
(2020, 2, 2),
(2020, 2, 6),
1,
&[(2020, 2, 3, 1.0), (2020, 2, 4, 2.0), (2020, 2, 5, 3.0)],
&[(2020, 2, 2, 1.0), (2020, 2, 3, 3.0), (2020, 2, 4, 6.0), (2020, 2, 5, 5.0), (2020, 2, 6, 3.0)],
)]
#[case::nonzero_radius_multiple_missing_days_1(
(2020, 2, 3),
(2020, 2, 7),
1,
&[(2020, 2, 3, 1.0), (2020, 2, 7, 1.0)],
&[(2020, 2, 3, 1.0), (2020, 2, 4, 1.0), (2020, 2, 5, 0.0), (2020, 2, 6, 1.0), (2020, 2, 7, 1.0)],
)]
#[case::nonzero_radius_multiple_missing_days_2(
(2020, 2, 3),
(2020, 2, 9),
1,
&[(2020, 2, 3, 1.0), (2020, 2, 9, 1.0)],
&[(2020, 2, 3, 1.0), (2020, 2, 4, 1.0), (2020, 2, 5, 0.0), (2020, 2, 6, 0.0), (2020, 2, 7, 0.0), (2020, 2, 8, 1.0), (2020, 2, 9, 1.0)]
)]
fn centered_moving_total(
#[case] start: (i32, u32, u32),
#[case] end: (i32, u32, u32),
Expand Down

0 comments on commit 4572cdb

Please sign in to comment.